Events

The ContactsManager SDK provides comprehensive event management features to enhance your app with social events and activities. Events can be used for scheduling meetups, announcing product launches, organizing team activities, or any other social interactions within your app. This guide explains how to implement these features using the SDK.

Event Flexibility

The event system in ContactsManager is designed to be highly flexible, allowing you to model various types of social interactions beyond traditional calendar events. Using the eventType field (a free-form string) and the metadata field (a customizable JSON object), you can implement a wide range of social features:

Use Cases for Events

  • Calendar Events: Traditional meetups, appointments, or gatherings with time and location
  • Social Posts: Status updates, photos, or announcements similar to Instagram or Facebook posts
  • Activity Feed Items: Transactions or actions like those in Venmo (“Alex paid Bob $20”)
  • Product Launches: Announcements of new features or products
  • Milestones: Achievements or important dates for users
  • Content Recommendations: Suggested articles, videos, or other content
  • Group Activities: Team events, group challenges, or collaborative projects

Customizing Events with Metadata

The metadata field accepts any JSON data, making it extremely versatile:

// Social post with image URL
let postMetadata: [String: Any] = [
    "postType": "image",
    "imageUrl": "https://example.com/images/vacation.jpg",
    "filter": "summer",
    "likes": 0,
    "commentsEnabled": true
]

// Transaction activity
let transactionMetadata: [String: Any] = [
    "amount": 25.50,
    "currency": "USD",
    "transactionId": "tx_12345",
    "category": "dining"
]

// Product recommendation
let recommendationMetadata: [String: Any] = [
    "productId": "prod_789",
    "discount": "15%",
    "expiryDate": "2025-05-01",
    "targetSegment": "new_users"
]

By creatively using these fields, you can build rich social features that perfectly match your app’s requirements without being limited to a rigid event structure.

Creating an Event

Creating events allows users to share activities and gatherings with their network. Events contain essential information like title, location, time, and visibility settings that determine who can see them.

do {
    let eventData = CreateEventRequest(
        eventType: "meeting",
        title: "Team Sync",
        description: "Weekly team synchronization",
        location: "Conference Room A",
        startTime: Date().addingTimeInterval(86400),  // Tomorrow
        endTime: Date().addingTimeInterval(90000),    // Tomorrow + 1 hour
        metadata: ["department": "Engineering"],
        isPublic: true
    )
    
    let result = try await ContactsService.shared.socialService.createEvent(
        eventData: eventData
    )
    
    if let eventId = result.eventId, let created = result.created, created {
        print("Created event with ID: \(eventId)")
    }
} catch {
    print("Error creating event: \(error.localizedDescription)")
}

The CreateEventRequest model includes these key properties:

  • eventType: A categorical identifier for the event (e.g., meeting, social, post, transaction)
  • title: The main title of the event
  • description: Detailed information about the event
  • location: Where the event will take place
  • startTime and endTime: When the event begins and ends
  • metadata: Additional custom data as key-value pairs
  • isPublic: Determines whether the event is visible to all users or only specific contacts

Example: Creating a Social Post Event

// Create a social post similar to Instagram
let socialPostEvent = CreateEventRequest(
    eventType: "social_post",
    title: "Beach day!",
    description: "Having an amazing time at Malibu",
    location: "Malibu Beach, CA",
    startTime: Date(),  // Post time is now
    metadata: [
        "postType": "photo",
        "imageUrl": "https://example.com/photos/beach.jpg",
        "filter": "vivid",
        "mentions": ["user_123", "user_456"],
        "hashtags": ["summer", "beach", "vacation"]
    ],
    isPublic: true
)

Example: Creating a Transaction Activity

// Create a transaction event like Venmo
let transactionEvent = CreateEventRequest(
    eventType: "transaction",
    title: "Dinner payment",
    description: "Thanks for dinner last night!",
    startTime: Date(),
    metadata: [
        "amount": 45.50,
        "currency": "USD", 
        "paymentMethod": "credit_card",
        "category": "dining",
        "recipients": ["user_789"],
        "emoji": "🍕"
    ],
    isPublic: true
)

Retrieving Event Details

Once an event is created, users can view its details. This is useful for displaying event information on dedicated event pages or when users receive event invitations.

do {
    let event = try await ContactsService.shared.socialService.getEvent(
        eventId: "event-id"
    )
    
    print("Event: \(event.title)")
    print("Type: \(event.eventType)")
    print("Description: \(event.description ?? "No description")")
    print("Location: \(event.location ?? "No location")")
    
    if let startTime = event.startTime {
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        formatter.timeStyle = .short
        print("Start time: \(formatter.string(from: startTime))")
    }
    
    // Access custom metadata
    if let metadata = event.metadata as? [String: Any] {
        // Handle different event types based on the metadata
        switch event.eventType {
        case "social_post":
            if let imageUrl = metadata["imageUrl"] as? String {
                print("Post image: \(imageUrl)")
            }
            if let hashtags = metadata["hashtags"] as? [String] {
                print("Hashtags: \(hashtags.joined(separator: ", "))")
            }
        case "transaction":
            if let amount = metadata["amount"] as? Double, 
               let currency = metadata["currency"] as? String {
                print("Amount: \(amount) \(currency)")
            }
        default:
            print("Other event type with metadata: \(metadata)")
        }
    }
} catch {
    print("Error retrieving event: \(error.localizedDescription)")
}

The retrieved SocialEvent object contains all the event properties including the creator’s information and event visibility settings, allowing you to build rich event detail interfaces.

Updating an Event

Event details may need to be modified after creation. Changes to time, location, or other details can be easily updated using the update method.

do {
    let updateData = UpdateEventRequest(
        title: "Updated Team Sync",
        description: "Updated weekly team synchronization",
        location: "Virtual Meeting Room",
        startTime: Date().addingTimeInterval(90000),
        endTime: Date().addingTimeInterval(93600),
        metadata: ["department": "Engineering", "priority": "High"],
        isPublic: true
    )
    
    let result = try await ContactsService.shared.socialService.updateEvent(
        eventId: "event-id",
        eventData: updateData
    )
    
    if let success = result.success, success {
        print("Successfully updated event")
    }
} catch {
    print("Error updating event: \(error.localizedDescription)")
}

The UpdateEventRequest model is similar to the creation model, but all fields are optional. Only the properties you want to change need to be included, and existing properties will remain unchanged if not specified.

Example: Updating a Social Post Event

// Update a social post with likes and comments
let updateSocialPost = UpdateEventRequest(
    metadata: [
        "likes": 42,
        "comments": [
            ["userId": "user_123", "text": "Amazing view!", "timestamp": Date().timeIntervalSince1970],
            ["userId": "user_456", "text": "Wish I was there!", "timestamp": Date().timeIntervalSince1970]
        ]
    ]
)

Deleting an Event

When events are cancelled or no longer needed, they can be removed from the system, which prevents them from appearing in feeds and search results.

do {
    let result = try await ContactsService.shared.socialService.deleteEvent(
        eventId: "event-id"
    )
    
    if let success = result.success, success {
        print("Successfully deleted event")
    }
} catch {
    print("Error deleting event: \(error.localizedDescription)")
}

Deleting an event is a permanent operation, so consider implementing confirmation flows in your app UI to prevent accidental deletions.

Event Feeds

The SDK provides different feed types to display events from users. These feeds are essential for creating engaging social experiences within your app.

Following Feed

The Following Feed shows events from users the user follows, creating a personalized stream of relevant activities. This helps users stay informed about what people in their network are doing.

do {
    let feed = try await ContactsService.shared.socialService.getFeed(
        skip: 0,
        limit: 20
    )
    
    print("Your feed has \(feed.total) events")
    
    for event in feed.items {
        print("\(event.title) by \(event.creatorName ?? "Unknown")")
        
        if let startTime = event.startTime {
            let formatter = DateFormatter()
            formatter.dateStyle = .medium
            formatter.timeStyle = .short
            print("When: \(formatter.string(from: startTime))")
        }
        
        // Handle different event types in the feed
        if let metadata = event.metadata as? [String: Any] {
            switch event.eventType {
            case "social_post":
                print("Post: \(event.description ?? "")")
                if let imageUrl = metadata["imageUrl"] as? String {
                    // Load and display image
                    print("Image: \(imageUrl)")
                }
            case "transaction":
                if let amount = metadata["amount"] as? Double,
                   let currency = metadata["currency"] as? String,
                   let emoji = metadata["emoji"] as? String {
                    print("\(emoji) \(amount) \(currency)")
                }
            default:
                print("Event: \(event.eventType)")
            }
        }
    }
} catch {
    print("Error retrieving feed: \(error.localizedDescription)")
}

”For You” Feed

The “For You” feed includes all public events in the user’s network, not limited to people they follow. This broader feed helps users discover new connections and activities they might be interested in.

do {
    let forYouFeed = try await ContactsService.shared.socialService.getForYouFeed(
        skip: 0,
        limit: 20
    )
    
    print("For You feed has \(forYouFeed.total) events")
    
    // Display events
    for event in forYouFeed.items {
        print("\(event.title) - \(event.eventType)")
    }
} catch {
    print("Error retrieving For You feed: \(error.localizedDescription)")
}

Upcoming Events

The Upcoming Events feed shows events scheduled in the future, helping users plan ahead and manage their calendar. This is particularly useful for displaying “events near you” or “happening soon” sections.

do {
    let upcomingEvents = try await ContactsService.shared.socialService.getUpcomingEvents(
        skip: 0,
        limit: 20
    )
    
    print("Found \(upcomingEvents.total) upcoming events")
} catch {
    print("Error retrieving upcoming events: \(error.localizedDescription)")
}

User Events

This feed shows events created by a specific user, which is useful for profile pages or when browsing a particular user’s activities.

do {
    let userEvents = try await ContactsService.shared.socialService.getUserEvents(
        userId: "user-id",
        skip: 0,
        limit: 20
    )
    
    print("User has created \(userEvents.total) events")
} catch {
    print("Error retrieving user events: \(error.localizedDescription)")
}

All feed methods support pagination through the skip and limit parameters, allowing you to implement infinite scrolling or load-more functionality.

Filtering Events by Type

When building specialized feeds, you may want to filter events by type. This can be done by processing the events returned by the feed methods:

// Get only social post events
func getSocialPosts() async throws -> [SocialEvent] {
    let feed = try await ContactsService.shared.socialService.getFeed(
        skip: 0,
        limit: 50
    )
    
    // Filter to only include social posts
    return feed.items.filter { $0.eventType == "social_post" }
}

// Get only transaction events
func getTransactions() async throws -> [SocialEvent] {
    let feed = try await ContactsService.shared.socialService.getFeed(
        skip: 0,
        limit: 50
    )
    
    // Filter to only include transactions
    return feed.items.filter { $0.eventType == "transaction" }
}

Building an Event Interface

Here’s an example of building an event detail view with SwiftUI, demonstrating how to create a polished and functional event interface:

This example shows a complete event detail view with dynamic content based on event type. You can adapt this interface to match your app’s design and requirements.

EventDetailView.swift
struct EventDetailView: View {
    let eventId: String
    
    @State private var event: SocialEvent?
    @State private var isLoading = true
    @State private var error: Error?
    
    var body: some View {
        ScrollView {
            if isLoading {
                ProgressView("Loading event...")
                    .padding()
            } else if let error = error {
                VStack(spacing: 12) {
                    Image(systemName: "exclamationmark.triangle")
                        .font(.largeTitle)
                        .foregroundColor(.red)
                    
                    Text("Failed to load event")
                        .font(.headline)
                    
                    Text(error.localizedDescription)
                        .font(.subheadline)
                        .multilineTextAlignment(.center)
                        .foregroundColor(.secondary)
                    
                    Button("Try Again") {
                        loadEvent()
                    }
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(8)
                }
                .padding()
            } else if let event = event {
                // Event header
                VStack(alignment: .leading, spacing: 16) {
                    // Event type badge
                    Text(event.eventType.uppercased())
                        .font(.caption)
                        .padding(.horizontal, 12)
                        .padding(.vertical, 6)
                        .background(Color.blue.opacity(0.2))
                        .foregroundColor(.blue)
                        .cornerRadius(16)
                    
                    // Title
                    Text(event.title)
                        .font(.largeTitle)
                        .fontWeight(.bold)
                    
                    // Creator info
                    HStack {
                        Circle()
                            .fill(Color.gray.opacity(0.3))
                            .frame(width: 36, height: 36)
                        
                        VStack(alignment: .leading) {
                            Text("Created by")
                                .font(.caption)
                                .foregroundColor(.secondary)
                            Text(event.creatorName ?? "Unknown")
                                .font(.subheadline)
                        }
                        
                        Spacer()
                        
                        // Public/private indicator
                        if event.isPublic {
                            Label("Public", systemImage: "globe")
                                .font(.subheadline)
                        } else {
                            Label("Private", systemImage: "lock")
                                .font(.subheadline)
                        }
                    }
                    
                    Divider()
                    
                    // Event time
                    if let startTime = event.startTime {
                        HStack(alignment: .top) {
                            Image(systemName: "calendar")
                                .frame(width: 24)
                            
                            VStack(alignment: .leading) {
                                Text(formattedDate(startTime))
                                    .font(.headline)
                                
                                if let endTime = event.endTime {
                                    Text("\(formattedTime(startTime)) - \(formattedTime(endTime))")
                                        .font(.subheadline)
                                        .foregroundColor(.secondary)
                                } else {
                                    Text(formattedTime(startTime))
                                        .font(.subheadline)
                                        .foregroundColor(.secondary)
                                }
                            }
                            
                            Spacer()
                            
                            Button(action: {
                                // Add to calendar action
                            }) {
                                Text("Add to Calendar")
                                    .font(.subheadline)
                            }
                            .buttonStyle(.bordered)
                        }
                        
                        Divider()
                    }
                    
                    // Location
                    if let location = event.location, !location.isEmpty {
                        HStack(alignment: .top) {
                            Image(systemName: "location.fill")
                                .frame(width: 24)
                            
                            VStack(alignment: .leading) {
                                Text("Location")
                                    .font(.caption)
                                    .foregroundColor(.secondary)
                                Text(location)
                                    .font(.subheadline)
                            }
                            
                            Spacer()
                            
                            Button(action: {
                                // Open maps action
                            }) {
                                Text("Directions")
                                    .font(.subheadline)
                            }
                            .buttonStyle(.bordered)
                        }
                        
                        Divider()
                    }
                    
                    // Description
                    if let description = event.description, !description.isEmpty {
                        VStack(alignment: .leading, spacing: 8) {
                            Text("About")
                                .font(.headline)
                            
                            Text(description)
                                .font(.body)
                        }
                        
                        Divider()
                    }
                    
                    // Custom metadata display based on event type
                    if let metadata = event.metadata as? [String: Any] {
                        VStack(alignment: .leading, spacing: 8) {
                            Text("Details")
                                .font(.headline)
                            
                            // Render different UI elements based on event type
                            switch event.eventType {
                            case "social_post":
                                if let imageUrl = metadata["imageUrl"] as? String {
                                    // This would be an AsyncImage in a real app
                                    Text("Image: \(imageUrl)")
                                        .font(.caption)
                                }
                                if let hashtags = metadata["hashtags"] as? [String] {
                                    Text(hashtags.map { "#\($0)" }.joined(separator: " "))
                                        .font(.caption)
                                        .foregroundColor(.blue)
                                }
                            case "transaction":
                                if let amount = metadata["amount"] as? Double,
                                   let currency = metadata["currency"] as? String {
                                    HStack {
                                        Text("Amount:")
                                        Spacer()
                                        Text("\(amount) \(currency)")
                                            .fontWeight(.semibold)
                                    }
                                }
                                if let category = metadata["category"] as? String {
                                    HStack {
                                        Text("Category:")
                                        Spacer()
                                        Text(category.capitalized)
                                    }
                                }
                            default:
                                ForEach(Array(metadata.keys.sorted()), id: \.self) { key in
                                    if let value = metadata[key] {
                                        HStack {
                                            Text("\(key.capitalized):")
                                            Spacer()
                                            Text("\(String(describing: value))")
                                        }
                                    }
                                }
                            }
                        }
                        
                        Divider()
                    }
                    
                    // Action buttons
                    HStack {
                        Button(action: {
                            // Share event
                        }) {
                            HStack {
                                Image(systemName: "square.and.arrow.up")
                                Text("Share")
                            }
                            .frame(maxWidth: .infinity)
                        }
                        .buttonStyle(.bordered)
                        
                        Spacer()
                            .frame(width: 16)
                        
                        Button(action: {
                            // RSVP action
                        }) {
                            HStack {
                                Image(systemName: "checkmark.circle")
                                Text("RSVP")
                            }
                            .frame(maxWidth: .infinity)
                        }
                        .buttonStyle(.borderedProminent)
                    }
                }
                .padding()
            }
        }
        .navigationTitle("Event Details")
        .navigationBarTitleDisplayMode(.inline)
        .onAppear {
            loadEvent()
        }
    }
    
    private func loadEvent() {
        isLoading = true
        error = nil
        
        Task {
            do {
                let loadedEvent = try await ContactsService.shared.socialService.getEvent(
                    eventId: eventId
                )
                
                await MainActor.run {
                    self.event = loadedEvent
                    self.isLoading = false
                }
            } catch {
                await MainActor.run {
                    self.error = error
                    self.isLoading = false
                }
            }
        }
    }
    
    private func formattedDate(_ date: Date) -> String {
        let formatter = DateFormatter()
        formatter.dateStyle = .long
        formatter.timeStyle = .none
        return formatter.string(from: date)
    }
    
    private func formattedTime(_ date: Date) -> String {
        let formatter = DateFormatter()
        formatter.dateStyle = .none
        formatter.timeStyle = .short
        return formatter.string(from: date)
    }
}

This example demonstrates how to:

  • Load and display event details
  • Handle loading states and errors
  • Format date and time information
  • Create an intuitive layout with proper visual hierarchy
  • Implement action buttons for user interaction
  • Dynamically display different UI based on event type and metadata

Best Practices for Events

  1. Validation: Implement client-side validation for event data to ensure all required fields are filled correctly before submission
  2. Timezone Handling: Be explicit about timezones when displaying event times to avoid confusion for users in different locations
  3. Cancellation Flow: Implement a proper flow for cancelling or rescheduling events, including notifications to affected users
  4. Event Reminders: Provide options for users to set reminders for events, improving user engagement and attendance
  5. Calendar Integration: Make it easy for users to add events to their device calendars with a simple tap
  6. Pagination: Implement proper pagination for event lists to maintain performance with large datasets
  7. Event Types: Create a consistent taxonomy of event types across your app for better filtering and display
  8. Metadata Structure: Document your metadata structure for different event types to ensure consistency

Troubleshooting

Common Issues

  1. Date Formatting Issues

    • Ensure proper date formatting across different locales by testing on various device settings
    • Be explicit about timezones when working with event dates, especially for events with global attendance
  2. Event Visibility

    • Check the isPublic flag when creating events to ensure proper visibility settings
    • Ensure proper permissions are set for viewing private events, especially in multi-user contexts
  3. Loading Performance

    • Use pagination to improve performance when loading large event lists
    • Implement caching for frequently accessed events to reduce network requests
    • Consider prefetching upcoming events data to improve perceived performance