Layover

Layover API Reference

Authentication

AuthenticationService

Handles Sign in with Apple authentication.

protocol AuthenticationServiceProtocol: Sendable {
    var currentUser: User? { get async }
    var isAuthenticated: Bool { get async }
    
    func signInWithApple() async throws -> User
    func signOut() async throws
}

Methods:

Errors:

AuthenticationViewModel

ViewModel for authentication UI.

@MainActor
class AuthenticationViewModel: ObservableObject {
    @Published private(set) var currentUser: User?
    @Published private(set) var isAuthenticated: Bool
    @Published private(set) var isLoading: Bool
    @Published private(set) var errorMessage: String?
    
    func signInWithApple() async
    func signOut() async
    func checkCredentialState() async
}

Models

User

Represents a user in the Layover app.

struct User: LayoverModel {
    let id: UUID
    var appleUserID: String?
    var username: String
    var email: String?
    var avatarURL: URL?
    var isHost: Bool
    var isSubHost: Bool
}

Properties:


Room

Represents a room where users participate in activities.

struct Room: LayoverModel {
    let id: UUID
    var name: String
    var hostID: UUID
    var subHostIDs: Set<UUID>
    var participantIDs: Set<UUID>
    var activityType: RoomActivityType
    var maxParticipants: Int
    var isPrivate: Bool
    var createdAt: Date
    var metadata: [String: String]
}

Methods:


RoomActivityType

Types of activities available in rooms.

enum RoomActivityType: String, Codable, Sendable {
    case appleTVPlus = "tv_plus"
    case appleMusic = "music"
    case chess = "chess"
}

MediaContent

Represents media content (movies, shows, songs).

struct MediaContent: LayoverModel {
    let id: UUID
    var title: String
    var contentID: String
    var artworkURL: URL?
    var duration: TimeInterval
    var mediaType: MediaType
}

MediaType:

enum MediaType: String, Codable, Sendable {
    case movie
    case tvShow
    case song
    case album
    case playlist
}

Services

SharePlayService

Manages SharePlay sessions and coordination.

@MainActor
protocol SharePlayServiceProtocol: LayoverService {
    var currentSession: GroupSession<LayoverActivity>? { get }
    var isSessionActive: Bool { get }
    
    func startActivity(_ activity: LayoverActivity) async throws
    func leaveSession() async
    func setupPlaybackCoordinator(player: AVPlayer) async throws
}

Usage:

let service = SharePlayService()

// Start SharePlay
let activity = LayoverActivity(
    roomID: roomID,
    activityType: .appleTVPlus,
    customMetadata: ["roomName": "Movie Night"]
)
try await service.startActivity(activity)

// Setup playback coordination
try await service.setupPlaybackCoordinator(player: avPlayer)

// Leave session
await service.leaveSession()

RoomService

Manages room operations.

@MainActor
protocol RoomServiceProtocol: LayoverService {
    var rooms: [Room] { get }
    
    func createRoom(name: String, hostID: UUID, activityType: RoomActivityType) async throws -> Room
    func joinRoom(roomID: UUID, userID: UUID) async throws
    func leaveRoom(roomID: UUID, userID: UUID) async throws
    func promoteToSubHost(roomID: UUID, userID: UUID) async throws
    func demoteSubHost(roomID: UUID, userID: UUID) async throws
    func deleteRoom(roomID: UUID) async throws
    func fetchRooms() async throws -> [Room]
}

Usage:

let service = RoomService()

// Create room
let room = try await service.createRoom(
    name: "Game Night",
    hostID: userID,
    activityType: .chess
)

// Join room
try await service.joinRoom(roomID: room.id, userID: friendID)

// Promote to sub-host
try await service.promoteToSubHost(roomID: room.id, userID: friendID)

AppleTVService

Manages Apple TV+ content playback.

@MainActor
protocol AppleTVServiceProtocol: LayoverService {
    var currentContent: MediaContent? { get }
    var player: AVPlayer? { get }
    
    func loadContent(_ content: MediaContent) async throws
    func play() async
    func pause() async
    func seek(to time: TimeInterval) async
}

Usage:

let service = AppleTVService()

// Load content
let content = MediaContent(
    title: "Sample Movie",
    contentID: "movie-123",
    duration: 7200,
    mediaType: .movie
)
try await service.loadContent(content)

// Control playback
await service.play()
await service.pause()
await service.seek(to: 600) // 10 minutes

AppleMusicService

Manages Apple Music playback.

@MainActor
protocol AppleMusicServiceProtocol: LayoverService {
    var currentContent: MediaContent? { get }
    var isAuthorized: Bool { get async }
    
    func requestAuthorization() async throws
    func loadContent(_ content: MediaContent) async throws
    func play() async
    func pause() async
}

Usage:

let service = AppleMusicService()

// Request authorization
if !await service.isAuthorized {
    try await service.requestAuthorization()
}

// Load and play music
let song = MediaContent(
    title: "Sample Song",
    contentID: "song-456",
    duration: 240,
    mediaType: .song
)
try await service.loadContent(song)
await service.play()

ViewModels

RoomListViewModel

Manages room list and operations.

@MainActor
@Observable
final class RoomListViewModel: LayoverViewModel {
    private(set) var rooms: [Room]
    private(set) var isLoading: Bool
    private(set) var errorMessage: String?
    
    func loadRooms() async
    func createRoom(name: String, hostID: UUID, activityType: RoomActivityType) async
    func joinRoom(_ room: Room, userID: UUID) async
    func leaveRoom(_ room: Room, userID: UUID) async
    func deleteRoom(_ room: Room) async
}

Usage in SwiftUI:

struct MyView: View {
    @State private var viewModel = RoomListViewModel()
    
    var body: some View {
        List(viewModel.rooms) { room in
            Text(room.name)
        }
        .task {
            await viewModel.loadRooms()
        }
    }
}

AppleTVViewModel

Manages Apple TV+ viewing experience.

@MainActor
@Observable
final class AppleTVViewModel: LayoverViewModel {
    private(set) var currentContent: MediaContent?
    private(set) var isPlaying: Bool
    private(set) var isLoading: Bool
    var player: AVPlayer?
    
    func loadContent(_ content: MediaContent) async
    func play() async
    func pause() async
    func seek(to time: TimeInterval) async
    func togglePlayPause() async
}

AppleMusicViewModel

Manages Apple Music listening experience.

@MainActor
@Observable
final class AppleMusicViewModel: LayoverViewModel {
    private(set) var currentContent: MediaContent?
    private(set) var isPlaying: Bool
    private(set) var isAuthorized: Bool
    
    func requestAuthorization() async
    func loadContent(_ content: MediaContent) async
    func play() async
    func pause() async
    func togglePlayPause() async
}

Error Types

SharePlayError

enum SharePlayError: LocalizedError {
    case activationDisabled
    case cancelled
    case noActiveSession
    case unknown
}

RoomError

enum RoomError: LocalizedError {
    case roomNotFound
    case roomFull
    case notAuthorized
}

MediaError

enum MediaError: LocalizedError {
    case invalidURL
    case loadFailed
}

MusicError

enum MusicError: LocalizedError {
    case notAuthorized
    case authorizationDenied
    case loadFailed
}

GameError

enum GameError: LocalizedError {
    case noActiveGame
    case invalidPlayerCount
    case playerNotFound
    case invalidMove
    case notYourTurn
}

Views

ContentView

Main app navigation view.

struct ContentView: View

RoomRowView

Displays a room in a list.

struct RoomRowView: View {
    let room: Room
}

CreateRoomView

Sheet for creating new rooms.

struct CreateRoomView: View {
    let currentUser: User
    let onCreate: (String, RoomActivityType) async -> Void
}

AppleTVView

Apple TV+ viewing interface.

struct AppleTVView: View {
    let room: Room
    let currentUser: User
}

AppleMusicView

Apple Music listening interface.

struct AppleMusicView: View {
    let room: Room
    let currentUser: User
}

ChessView

Chess game interface.

struct ChessView: View {
    let room: Room
    let currentUser: User
}

Platform-Specific Features

iOS

macOS

tvOS

visionOS


Best Practices

Service Injection

// In production, inject services
let viewModel = RoomListViewModel(
    roomService: productionRoomService,
    sharePlayService: productionSharePlayService
)

// In tests, inject mocks
let viewModel = RoomListViewModel(
    roomService: mockRoomService,
    sharePlayService: mockSharePlayService
)

Error Handling

// Always handle errors
do {
    try await service.performAction()
} catch let error as RoomError {
    // Handle room-specific errors
    handleRoomError(error)
} catch {
    // Handle general errors
    handleGeneralError(error)
}

Async/Await

// Use Task for async operations in sync contexts
Button("Create Room") {
    Task {
        await viewModel.createRoom(name: name, hostID: hostID, activityType: type)
    }
}

@MainActor

// Services and ViewModels are @MainActor
@MainActor
func updateUI() {
    // Safe to update UI here
    viewModel.loadRooms()
}

Extension Points

Custom Activities

Extend with new activity types:

  1. Add to RoomActivityType
  2. Create service protocol
  3. Implement service
  4. Create ViewModel
  5. Build View
  6. Update ContentView routing

Custom Game Logic

Add new games:

  1. Create game model
  2. Implement game service
  3. Write comprehensive tests
  4. Create game ViewModel
  5. Build game View

Backend Integration

Replace in-memory storage:

  1. Implement network layer
  2. Add persistence
  3. Update services
  4. Handle synchronization
  5. Add offline support