Apple Business Chat消息扩展:交互消息移至应用区域实现方案
Hey Aaron, let's break down how to implement both of your desired behaviors for your Apple Business Chat Messages Extension, plus fix that tricky TableViewController issue in message bubbles.
This is the most common (and Apple-recommended) pattern for interactive Business Chat experiences, since it keeps the conversation clean until the user chooses to engage. Here's how to set it up:
First, create a static message bubble using
MSMessageTemplateLayoutfor the initial display:let staticLayout = MSMessageTemplateLayout() staticLayout.caption = "Available Options" staticLayout.subcaption = "Tap to explore" staticLayout.image = UIImage(named: "options-preview") // Optional: add a visual hint let triggerMessage = MSMessage() triggerMessage.templateLayout = staticLayout // Use a custom URL scheme to identify this message later triggerMessage.url = URL(string: "your-app-scheme://action=showOptions") // Insert the message into the conversation activeConversation?.insert(triggerMessage, completionHandler: { error in if let error = error { print("Failed to insert trigger message: \(error.localizedDescription)") } })Next, handle the bubble tap in your
MessagesViewController:override func didSelectMessage(_ message: MSMessage, conversation: MSConversation) { guard message.url?.absoluteString.contains("showOptions") == true else { return } // Load your TableViewController and present it in the extension let optionsVC = OptionsTableViewController() // If you want to replace the extension's root view instead of presenting: // optionsVC.view.frame = self.view.bounds // self.addChild(optionsVC) // self.view.addSubview(optionsVC.view) // optionsVC.didMove(toParent: self) // Or present it modally self.present(optionsVC, animated: true) }
If you don't need a static bubble and want the interactive UI to appear immediately when the user opens your extension:
- Skip sending a trigger message entirely. Instead, load your TableViewController directly in the
viewDidLoadof yourMessagesViewController:override func viewDidLoad() { super.viewDidLoad() setupInteractiveTableView() } private func setupInteractiveTableView() { let optionsVC = OptionsTableViewController() // Pin the table view to the extension's bounds optionsVC.view.translatesAutoresizingMaskIntoConstraints = false self.addChild(optionsVC) self.view.addSubview(optionsVC.view) optionsVC.didMove(toParent: self) NSLayoutConstraint.activate([ optionsVC.view.topAnchor.constraint(equalTo: self.view.topAnchor), optionsVC.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), optionsVC.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), optionsVC.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor) ]) }
This works best for immediate, context-specific interactions where you don't need to leave a trace in the conversation thread.
The core issue here is that message bubbles have strict size constraints imposed by the Messages app—they're not designed to host interactive components like table views. Here's how to work around this:
- Avoid embedding TableViews in bubbles entirely: Stick to the first pattern (static bubble + tap to expand) to give your table view enough space in the extension's dedicated UI area.
- If you must show list content in a bubble: Generate a static image of your options (using a graphics context) and set it as the
imageproperty ofMSMessageTemplateLayout. This keeps the bubble clean, and users still tap to open the interactive table view. - Optimize table view for narrow spaces: If you end up using a table view in a constrained area, tweak its layout for better usability:
class OptionsTableViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 60 // Helps with dynamic sizing tableView.contentInset = UIEdgeInsets(top: 12, left: 0, bottom: 12, right: 0) // Add breathing room tableView.separatorInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) // Clean up separators } }
内容的提问来源于stack exchange,提问作者Aaron Bratcher

