import Messages import SwiftUI import UIKit class MessagesViewController: MSMessagesAppViewController { private let gameController = GameController() private var hostingController: UIHostingController? private var currentSession: MSSession? override func viewDidLoad() { super.viewDidLoad() gameController.onRequestExpand = { [weak self] in self?.requestPresentationStyle(.expanded) } gameController.onRequestCollapse = { [weak self] in self?.requestPresentationStyle(.compact) } gameController.onShareGame = { [weak self] gameID, password, caption, newSession in self?.insertGameMessage( gameID: gameID, password: password, caption: caption, newSession: newSession ) } embedRootIfNeeded() } override func willBecomeActive(with conversation: MSConversation) { apply(conversation: conversation, style: presentationStyle) embedRootIfNeeded() } override func didResignActive(with conversation: MSConversation) { gameController.stopPolling() } override func didReceive(_ message: MSMessage, conversation: MSConversation) { apply(conversation: conversation, style: presentationStyle) } override func didCancelSending(_ message: MSMessage, conversation: MSConversation) { apply(conversation: conversation, style: presentationStyle) } override func willTransition(to presentationStyle: MSMessagesAppPresentationStyle) { gameController.presentationStyle = chrome(for: presentationStyle) } override func didTransition(to presentationStyle: MSMessagesAppPresentationStyle) { gameController.presentationStyle = chrome(for: presentationStyle) } private func apply(conversation: MSConversation, style: MSMessagesAppPresentationStyle) { if let selected = conversation.selectedMessage { currentSession = selected.session } gameController.configureMessages( style: chrome(for: style), remoteParticipantCount: conversation.remoteParticipantIdentifiers.count, gameURL: conversation.selectedMessage?.url ) } private func chrome(for style: MSMessagesAppPresentationStyle) -> PresentationChrome { style == .expanded ? .expanded : .compact } private func insertGameMessage( gameID: Int, password: String?, caption: String, newSession: Bool ) { guard let conversation = activeConversation else { gameController.errorMessage = "No active conversation to send into." return } let session = newSession ? nil : (conversation.selectedMessage?.session ?? currentSession) let message = MSMessage(session: session ?? MSSession()) message.url = MessageGameID.url(gameID: gameID, password: password) message.summaryText = caption let layout = MSMessageTemplateLayout() layout.caption = "Six Card Golf" layout.subcaption = caption layout.trailingCaption = "#\(gameID)" message.layout = layout currentSession = message.session // `send` delivers immediately (no compose-field Send tap), like Game Pigeon. conversation.send(message) { [weak self] error in DispatchQueue.main.async { if let error { self?.gameController.errorMessage = error.localizedDescription } } } } private func embedRootIfNeeded() { guard hostingController == nil else { return } let hosting = UIHostingController(rootView: RootView(controller: gameController)) hostingController = hosting addChild(hosting) hosting.view.translatesAutoresizingMaskIntoConstraints = false hosting.view.backgroundColor = .clear view.addSubview(hosting.view) NSLayoutConstraint.activate([ hosting.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), hosting.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), hosting.view.topAnchor.constraint(equalTo: view.topAnchor), hosting.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) hosting.didMove(toParent: self) } }