iOS 10中的互动消息

iOS 10 SDK引入了新的Messages框架,该框架使应用程序开发人员可以创建扩展程序以与Messages应用程序进行交互。 今天,我将说明如何使用Messages框架发送交互式消息。 我们将创建一个扩展,使两个用户可以使用Messages应用程序玩经典的井字游戏。

什么是互动讯息?

以下屏幕截图将使您了解交互式消息气泡的外观。 您可以在交互式消息中嵌入图像或视频。

创建消息扩展

通过扩展可以使用向Messages应用程序添加功能的功能。 因此,首先我们将打开Xcode,创建一个新的Xcode项目,然后选择Message扩展模板。

您将观察到创建了一个带有附加MessageExtension目标的新项目。 今天,我们将为此目标进行所有工作。

MSMessagesAppViewController

MessagesViewController.swift包含MessagesViewController类,它是MSMessagesAppViewController的子类。 此类是扩展的入口点和控制中心。

演讲风格

消息扩展有两种表示样式。

  1. 紧凑型-从应用程序抽屉启动扩展程序时,最初显示此样式。
  2. 扩展-您可以单击紧凑样式中的扩展箭头以过渡到扩展样式。 当发件人单击交互式消息之一时,也可以触发它。

应用逻辑

我们仅允许用户使用扩展的呈现方式玩此游戏。 因此,您可以单击一条消息,放置标记(x或0)。 一旦放置了标记,将包含表示游戏板当前状态的图像的消息插入到输入字段中。 用户可以控制是否要发送消息。 接收者收到消息后,可以单击该消息并继续前进。 用户移动后,显示新状态的图像将插入到输入字段中。

通过仅添加标签,我们将使紧凑的演示文稿样式保持简单。

项目概况

  1. GameBaordVC —此视图控制器以扩展的呈现方式呈现。 这将处理与玩游戏有关的所有功能。 GameBoardVC管理的视图具有collectionview。 轻触单元格可让用户在单元格上放置x或0标记。
  2. CollapsedVC —此视图控制器以紧凑的表示形式表示
  3. GameStateVM —此视图模型将保存有关游戏板当前状态的信息,并做出有关游戏结果的决定。

呈现消息扩展UI

MSMessagesAppViewController通过以下方法通知扩展的各种状态更改。

启动扩展时,将按指示的顺序调用以下方法。

  1. func willBecomeActive(与对话:MSConversation) 
  2. func didBecomeActive(与对话:MSConversation) 
  3. func viewWillAppear() 
  4. func viewDidAppear() 

MSConversation对象表示消息对话。 在上面列出的方法中,对话对象表示将要开始的对话。

以下是用于展示扩展程序的代码段。

 私人功能presentViewController(用于会话:MSConversation,具有presentationStyle:MSMessagesAppPresentationStyle){ 
  //确定要显示的控制器。 
 让控制器:UIViewController 
 如果presentationStyle == .compact { 
 控制器= InstantiateVCWithIdentifier(identifier:“ StateStickersVC”) 
  } 
 其他{ 
 让gameBoardVM = GameStateVM(message:session.selectedMessage) 
 控制器= InstantiateVCWithIdentifier(identifier:“ GameBoardVC”) 
 让gameBoardVC =控制器作为!  GameBoardVC 
  gameBoardVC.gameStateVM = gameBoardVM 
  gameBoardVC.delegate =自我 
  } 
  removeAllChildViewControllers() 
  embedViewController(viewController:控制器) 
  } 

消息扩展名也可以通过直接点击交互式消息来启动。 这将以扩展的演示样式启动扩展。

在上面的函数中,如果演示文稿样式被扩展,我们将GameBoardVC初始化,将其添加为MessagesViewController的子视图控制器,并将其嵌入扩展视图中。

我们还从视图层次结构中删除了以扩展或紧凑形式显示的控制器视图的所有先前实例。

我们将GameStateVM的实例与GameBaordVC关联。

我们使用当前对话的选定消息初始化GameStateVM。

 便捷初始化(消息:MSMessage?){ 
 警卫让messageURL = message?.url else { 
  self.init(queryItems:[]) 
 返回 
  } 
 保护urlComponents = NSURLComponents(url:messageURL,resolvingAgainstBaseURL:false),queryItems = urlComponents.queryItems else { 
  self.init(queryItems:[]) 
 返回 
  } 
  self.init(queryItems:queryItems) 
  } 
  init(queryItems:[URLQueryItem]){ 
  gameStateArray = [Int](重复:0,计数:(rowCount * 2)+ 2) 
 保护queryItems.count> 0,否则{ 
 玩家= 1 
  self.queryItems = [URLQueryItem](重复:URLQueryItem(名称:“ 0”,值:“ 0”),计数:rowCount * rowCount) 
 返回 
  } 
 对于0中的i ... queryItems.count-1 { 
 让queryItem = queryItems [i] 
 让玩家= Int(queryItem.name) 
 如果i == queryItems.count-1 { 
  self.player =玩家!  == 1吗?  -1:1 
  } 
 其他{ 
  self.queryItems.append(queryItem) 
  } 
  } 
 用于0 ... self.queryItems.count中的索引-1 { 
  changeGameArrayWithRow(行:索引/ rowCount,列:索引%rowCount,播放器:Int(self.queryItems [index] .value!)!) 
  } 
  } 

使用Message Extension发送消息时,您可以传输的唯一数据是将URL传递给我们编写的MSMessage,以发送到另一端。 如果您需要传递任何其他信息,则必须使用API​​。 我们需要传递baord的当前状态。 为简单起见,我们将通过MSMessage将游戏面板状态作为行主要有序数组传递。 我们将其作为queryItems传递:[URLQueryItem]? NSURLComponents。 因此,为了方便起见, init(message:MSMessage?)我们检查了必须回复的选定消息是否具有queryItems 。 如果不是,我们使用self.init(queryItems:[])将其初始化为游戏板的初始状态。

我们必须知道GameStateVM的三个实例变量

  var gameStateArray:[Int] = [] 
  var player:Int = 0 
  var queryItems:[URLQueryItem] = [] 

queryItems:[URLQueryItem]保存代表所选消息的游戏板的状态。 它按行主要顺序保留x和0的位置。 为简单起见,我们在数组中将玩家x表示为1,将玩家0表示为-1。

URLQueryItem具有名称和值。 对应于当前玩家的整数值1 0r -1转换为字符串,并存储在value参数中的给定位置。 当游戏板为空时,所有查询项的值和名称都初始化为0。当玩家移动时,根据与玩家相关联的整数值,我们在该位置更改查询项的名称值对。

queryItems的 rowCount * columnCount项数加上1表示哪个玩家进行了最后一个移动(x或0)。 如果最后一步的玩家是x(1),那么我们将当前玩家分配为0(-1),反之亦然。 我们的示例使用3×3板。

gameStateArray:[Int]将有一个条目,分别代表游戏板的所有行,列和两个对角线。 因此,项目数是rowCount + ColumnCount + 2(对角线)。

当玩家根据他是x(1)还是o(-1)进行移动时,我们会将其得分添加到表示他在gameStateArray中进行移动的像元的行,列和对角线。 例如:如果他在第0列第0行中移动,我们必须在gameStateArray [0](gameStateArray [row],gameStateArray [rowCount + col],gameStateArray [rowCount * 2])的得分上加上+1。

我们根据queryItems中的位置计算行数和列数。

此功能在以下位置完成: func changeGameArrayWithRow(row:Int,column col:Int,player:Int)

建立游戏板

GameBoardVC具有代表游戏板的集合视图。 它还有一个标签,显示游戏是否结束。 集合视图的数据源是GameStateVM的queryItems。

GameStateVM有一个方法defineWinner()-> Int ,它根据当前游戏板来决定游戏的结果。

  func defineWinner()-> Int { 
 用于gameStateArray中的状态{ 
 如果状态== -GameBoardVC.rowCount { 
 返回-1 
  } 
 否则,如果state == GameBoardVC.rowCount { 
 返回1 
  } 
  } 
 返回0 
  } 

funDecateWinner()->如果x赢得了比赛,则Int返回1;如果0赢得了游戏,则Int返回-1,否则返回0。 指示游戏进度的标签被隐藏或相应显示。

采取行动

在选择集合视图的任何单元格时,如果该单元格为空,我们将放置与当前玩家关联的标记。 如果单元格不为空,则不要让用户移动。 同样,如果用户已经采取了行动,我们将禁止他采取其他行动。

  func collectionView(_ collectionView:UICollectionView,didSelectItemAt indexPath:IndexPath){ 
 警惕让playValue = gameStateVM.queryItems [indexPath.item!]。value,intValue = Int(playedValue),其中intValue == 0 && moveMade == false && gameStateVM.decideWinner()== 0 else { 
 返回 
  } 
  gameStateVM.placeItemAtRow(行:indexPath.item!/ gameStateVM.rowCount,列:indexPath.item!%gameStateVM.rowCount,玩家:gameStateVM.player) 
  collectionView.reloadData() 
  moveMade = true 
 让图像= collectionView.takeSnapshot() 
 委托?.selecteWithGameStateVM(gameStateVM:gameStateVM,image:image) 
  } 

我们还将截取游戏板当前状态的屏幕快照,以将其插入我们发送给其他玩家的消息中。 这将在视觉上代表游戏的当前状态。 在此示例中,我扩展了UIView,以添加用于此目的的方法funSnapSnapshot()。

 扩展UIView { 
  func takeSnapshot()-> UIImage { 
  UIGraphicsBeginImageContextWithOptions(bounds.size,false,UIScreen.main()。scale) 
  drawHierarchy(in:bounds,afterScreenUpdates:true) 
 让图像= UIGraphicsGetImageFromCurrentImageContext() 
  UIGraphicsEndImageContext() 
 返回图片! 
  } 
  } 

发送信息

最后,我们处于实施的最后阶段,即发送实际消息。

当玩家移动时,我们必须通知我们的MessagesViewController将消息插入输入字段。 为此,我们必须向GameBaordVC添加一个Delegate。

 协议GameBoardVCDelegate { 
  func selectWithGameStateVM(gameStateVM:GameStateVM,图片:UIImage) 
  } 
  func selectWithGameStateVM(gameStateVM:GameStateVM,image:UIImage){ 
 警卫让对话= activeConversation否则{fatalError(“预期的对话”)} 
  var组件= URLComponents() 
  components.queryItems = gameStateVM.queryItems 
  components.queryItems?.append(URLQueryItem(name:“ \(gameStateVM.player)”,值:“ 0”)) 
 让布局= MSMessageTemplateLayout() 
  layout.image =图片 
  layout.caption =“游戏开启” 
 如果gameStateVM.decideWinner()!= 0 { 
  layout.caption =“游戏结束” 
  } 
  let message = MSMessage(会话:self.activeConversation?.selectedMessage?.session ?? MSSession()) 
  message.url = components.url! 
  message.layout =布局 
  session.insert(message,localizedChangeDescription:nil){(错误)在 
  } 
  } 
 解雇() 
  } 

我们从GameStateVM中的queryItems构造NSURLComponents 。 我们还添加了一个queryItem,它表示与做出此举的玩家相关的值。

我们初始化一个MessageTemplateLayout() ,它描述要插入到输入字段中的消息的布局。 游戏板屏幕截图将成为布局图像,并且根据游戏是否结束而更改标题。

要插入实际消息,我们构造一个MSMessage 。 如果正在进行对话,则由MSSession标识。 该会话对象用于初始化消息。 否则,我们将初始化一个新的MSSession对象,并将其与要发送的消息相关联。 我们之前构建的模板布局也与MSMessage相关联。

最后,我们使用函数将消息插入对话:

公共功能插入(_消息:MSMessage,localizedChangeDescription changeDescription:字符串?,complementHandler:(((NSError?)-> Swift.Void)?= nil)

这完成了使用Messages框架创建简单的井字游戏所需的步骤。

这是我们的最终产品。

您可以观看wwdc视频以获取交互式消息,并参考消息框架以获取详细参考。

请随时查看源代码。

由Y Labs创新实验室开发