Post

Apple Watch App 开发教学|从 UI 排版到 WatchConnectivity 资料同步完整攻略

针对有 iOS UIKit 基础的开发者,掌握 Apple Watch App 简化 UI 排版、WatchConnectivity 双向资料传递与推播实作,解决第三方 App 功能不足痛点,快速打造辅助型手表应用,提升使用体验与即时互动效率。

Apple Watch App 开发教学|从 UI 排版到 WatchConnectivity 资料同步完整攻略

Click here to view the English version of this article.

點擊這裡查看本文章正體中文版本。

基于 SEO 考量,本文标题与描述经 AI 调整,原始版本请参考内文。


动手做一支 Apple Watch App 吧!(Swift)

watchOS 5 手把手开发Apple Watch App 从无到有

[最新] Apple Watch Series 6 开箱&使用两年体验心得 >>>点我前往

前言:

暨上一篇 Apple Watch 入手开箱文 后已经过了快三个月,最近终于找到机会研究开发Apple Watch App啦。

[结婚吧 — 最大婚礼筹备App](https://itunes.apple.com/tw/app/%E7%B5%90%E5%A9%9A%E5%90%A7-%E4%B8%8D%E6%89%BE%E6%9C%80%E8%B2%B4-%E5%8F%AA%E6%89%BE%E6%9C%80%E5%B0%8D/id1356057329#?platform=appleWatch){:target="_blank"}

结婚吧 — 最大婚礼筹备App

补一下使用三个月后的心得:

  1. e-sim(LTE)依然还想不到什么时候会用到,所以也还没申请没用过 2.常用功能:靠近解锁Mac电脑、举手查看通知、Apple Pay 3.健康提醒:过了三个月已开始懒了,通知提醒都看看,没达成圆圈也无感 4.第三方App支援度依然很差 5.表面可依照心情任意更换增加新鲜感 6.更详细的运动纪录:例如走远一点路去买晚餐,手表会自动侦测询问是否要记录运动

使用三个月后整体来说,还是如原开箱文所写就像是多个生活小助手,帮你解决琐碎的事.

第三方App支援度依然很差

在我实际开发过Apple Watch App之前还很纳闷,为何Apple Watch上的App都很阳春甚至就只是「堪用」罢了,包括LINE(讯息不同步而且从未更新)、Messenger(就是堪用);直到我实际开发过Apple Watch App之后才知道这些开发者的苦衷….

首先,了解Apple Watch App的定位,化繁为简

Apple Watch的定位 「不是取代iPhone,而是辅助」 不论是官方介绍、官方App、watchOS API都是这个走向;所以才会觉得第三方APP很阳春、功能很少(抱歉,我太贪心了Orz)

我们的A pp为例,有搜寻商家、查看专栏、讨论区、线上询问…等等功能;线上询问就是有价值搬上Apple Watch的项目,因为他需要即时性而且更快速的回复代表更有机会获得订单;搜寻商家、查看专栏、讨论区这些功能相对复杂,在手表上就算做的到也意义不大(萤幕能呈现的资讯太少、也不需要即时性)

核心概念还是「以辅助为主」,所以并不是什么功能都需要搬上Apple Watch;毕竟使用者很少很少时间会是只有戴手表没带手机,而遇到这种情况时,使用者的需求也只有重要的功能(像查看专栏文章这种没有重要到一定要立刻马上用手表看)

让我们开始吧!

这也是我第一次开发Apple Watch App,文章内容可能不够深入,敬请大家指教!!

本篇只适合有开发过iOS App/UIKit基础的读者阅读

本篇使用:iOS ≥ 9、watchOS ≥ 5

为iOS专案新建 watchOS Target:

File -> New -> Target -> watchOS -> WatchKit App

File -> New -> Target -> watchOS -> WatchKit App

*Apple Watch App无法独立安装,一定要依附在 iOS App 之下

新建好之后目录会长这样:

你会发现有两个Target项目,缺一不可:

  1. WatchKit App: 负责存放资源、UI显示 /Interface.storyboard:同 iOS,里面有系统预设建立的视图控制器 /Assets.xcassets:同 iOS,存放用到的资源项目 /info.plist:同 iOS,WatchKit App 相关设定

  2. WatchKit Extension: 负责程式呼叫、逻辑处理( * .swift) /InterfaceController.swift:预设的视图控制器程式 /ExtensionDelegate.swift:类似Swift的AppDelegate,Apple Watch App 启动入口 /NotificationController.swift:用于处理Apple Watch App上的推播显示 /Assets.xcassets:这里不使用,我统一放在WatchKit App的Assets.xcassets下 /info.plist:同 iOS,WatchKit Extension 相关设定 /PushNotificationPayload.apns:推播资料,可用在模拟器上测试推播功能

细节会在后面做介绍,先大概了解一下目录及文件内容功能即可。

视图控制器:

在AppleWatch中视图控制器不叫ViewController而是InterfaceController ,你可以在WatchKit App/Interface.storyboard中找到Interface Controller Scence,控制它的程式就放在WatchKit Extension/InterfaceController.swift中(同iOS概念)

Scene预设会和Notification Controller Scene挤在一起 (我会把它拉上面一点分开)

Scene预设会和Notification Controller Scene挤在一起 (我会把它拉上面一点分开)

可在右方设定InterfaceController的标题显示文字.

标题颜色部分吃的是Interface Builder Document/Global hint设定,整个App的风格颜色会是统一的.

元件库:

没有太多复杂的元件,元件功能也都简单明了

没有太多复杂的元件,元件功能也都简单明了

UI 排版:

万丈高楼从View起,排版的部分没有 UIKit(iOS) 中的Auto Layout、约束、图层,全都使用参数进行排版设置,更简单有力(排起来有点像 UIKit 中的 UIStackView)

一切排版由Group组成,类似UIKit中的 UIStackView 但能设置更多排版参数

Group的参数设置

Group的参数设置

  1. Layout:设置被包在里面的子View排版方式(水平、垂直、图层堆叠)

  2. Insets:设置Group的上下左右间距

  3. Spacing:设置被包在里面的子View之间的间距

  4. Radius:设置Group的圆角,没错!WatchKit自带圆角设置参数

  5. Alignment/Horizontal:设置水平对齐方式(左、中、右)与邻居、外层包覆的View设置会有所连动

  6. Alignment/Vertical:设置垂直对齐方式(上、中、下)与邻居、外层包覆的View设置会有所连动

  7. Size/Width:设置Group的大小,有三种模式可选「Fixed:指定宽度」、「Size To Fit Content:依照内容子View大小决定宽度」、「Relative to Container:参照外层包覆的View大小为宽度(可设%/+ -修正值)」

  8. Size/Height:同Size/Width,此项是设置高度

字型/字体大小设置:

可直接套用系统的Text Styles,或使用Custom(但这边我测试使用Custom无法设定字体大小);所以 我是使用System 自订各显示Label的字体大小

做中学:以Line排版为例

排版部分不像 iOS 那么复杂,所以我直接透过范例示范给大家看,就能直接上手;以 Line 的主页排版为例子:

在WatchKit App/Interface.storyboard中找到Interface Controller Scence:

1.整个页面,相当于 iOS App 开发中会使用到的 UITableView,在Apple Watch App 中简化了操作,名字也改叫做「WKInterfaceTable」 首先就先拉一个Table到Interface Controller Scence中

同UIKit UITableView,有Table本体、有Cell(Apple Watch中叫做Row);使用起来简化许多, 你可以直接在此介面上进行Cell的设计排版!

  1. 分析排版架构,设计Row显示样式:

要排出一个左边有圆角满版的Image且堆叠一个Label,右边平均分配上下两个区块,上方放Label,下方也放Label的区块

2–1: 拉出左右两区块的架构

拉两个Group到Group中,并对Size参数分别设定:

左边绿色部分:

Layout设定Overlap,里面子View要做未读讯息Label的图层堆叠显示

Layout设定Overlap,里面子View要做未读讯息Label的图层堆叠显示

设固定长宽40的正方形

设固定长宽40的正方形

右边红色部分:

Layout设定Vertical,里面子View要做上下两个显示

Layout设定Vertical,里面子View要做上下两个显示

宽度设定参照外层,比例100%,扣掉左边绿色部分40

宽度设定参照外层,比例100%,扣掉左边绿色部分40

左右容器内排版:

左边部分:拉入一个Image,再拉入一个包覆Lable的Group对齐设右下(Group设底色再设间距及圆角)

右边部分:拉入两个Label,一个对齐设左上,一个对齐设左下即可

为Row命名(同UIKit UITableView为Cell设定identifier):

选定Row->Identifier->输入自订名称

选定Row->Identifier->输入自订名称

Row的呈现样式不只一种呢?

非常简单,只要在拉一个Row放在Table里(实际要显示哪个样式的ROW由程式控制)并输入Identifier命名即可

这边我再拉一个Row用于呈现无资料时的提示

这边我再拉一个Row用于呈现无资料时的提示

排版相关资讯

watchKit的hidden不会占位,可拿来做交互应用(有登入才显示Table;没登入显示提示Label)

排版到此告一段落,可依照个人设计做修改;上手容易,多排个几次、玩玩对齐参数,就能熟悉!

程式控制部分:

接续Row,我们需要建立一个Class对Row进行参照操作:

1
2
class ContactRow:NSObject {
}

1
2
3
4
5
6
7
8
class ContactRow:NSObject {
    var id:String?
    @IBOutlet var unReadGroup: WKInterfaceGroup!
    @IBOutlet var unReadLabel: WKInterfaceLabel!
    @IBOutlet weak var imageView: WKInterfaceImage!
    @IBOutlet weak var nameLabel: WKInterfaceLabel!
    @IBOutlet weak var timeLabel: WKInterfaceLabel!
}

拉outlet、储存变数

Table部分ㄧ样拉Outlet到Controller中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class InterfaceController: WKInterfaceController {

    @IBOutlet weak var Table: WKInterfaceTable!
    override func awake(withContext context: Any?) {
        super.awake(withContext: context)
        
        // Configure interface objects here.
    }
    
    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
    }
    
    struct ContactStruct {
        var name:String
        var image:String
        var time:String
    }
    
    func loadData() {
        //Get API Call Back...
        //postData {
        let data:[ContactStruct] = [] //api returned data...
        
        self.Table.setNumberOfRows(data.count, withRowType: "ContactRow")
        //如果你有多种ROW需要呈现则用:
            //self.Table.setRowTypes(["ContactRow","ContactRow2","ContactRow3"])
        //
        for item in data.enumerated() {
            if let row = self.Table.rowController(at: item.offset) as? ContactRow {
                row.nameLabel.setText(item.element.name)
                //assign value to lable/image......
            }
        }
        
        //}
    }
    
    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
        loadData()
    }
    
    //处理Row点选时:
    override func table(_ table: WKInterfaceTable, didSelectRowAt rowIndex: Int) {
        guard let row = table.rowController(at: rowIndex) as? ContactRow,let id = row.id else {
            return
        }
        self.pushController(withName: "showDetail", context: id)
    }
}

Table的操作简化许多没有delegate/datasource,设定资料方式只要呼叫setNumberOfRows/setRowTypes指定Row数量和形态,再使用rowController(at:) 设定每列的资料内容即可!

Table的Row选择事件也只需 override func table( _ table: WKInterfaceTable, didSelectRowAt rowIndex: Int) 即可操作!(Table也只有这个事件)

如何跳页?

首先为Interface Controller设定Identifier

首先为Interface Controller设定Identifier

watchKit有两种跳页模式:

1.类似iOS UIKit push self.pushController(withName: Interface Controller Identifier , context: Any? )

push方式可左上返回

push方式可左上返回

返回上一页同iOS UIKit:self.pop( )

返回根页面:self.popToRootController( )

开新页面:self.presentController( )

  1. 页签显示方式 WKInterfaceController.reloadRootControllers(withNames: [ Interface Controller Identifier ], contexts: [ Any? ] )

亦或是在Storyboard上,在第一页的Interface Controller上按Control+Click拖曳到第二页选择「next page」也可

页签显示方式可以左右切换页面

页签显示方式可以左右切换页面

两种跳页方式不能混用.

跳页参数?

不像iOS需要使用自订delegate或segue方式传递参数,watchKit跳页带参数方式就是将参数放入上方方法中的 contexts 中即可.

接收参数在 InterfaceController 的 awake(withContext context: Any?)

例如我在A页面要跳到B页面并带入id:Int时:

A 页面:

1
self.pushController(withName: "showDetail", context: 100)

B 页面:

1
2
3
4
5
6
7
8
9
override func awake(withContext context: Any?) {
        super.awake(withContext: context)
        guard let id = context as? Int else {
           print("参数错误!")
           self.popToRootController()
           return
        }
        // Configure interface objects here.
}

程式控制元件部分

相比iOS UIKit一样简化许多,有开发过iOS的应该上手很快! 例如label变成setText( ) p.s. 而且居然没有getText的方法,只能extension变数或放在外部变数储存

与iPhone之间同步/资料传递

如果有开发过iOS 相关 Extension 的话;下意识一定是用App Groups共享UserDefaults的方式,当初我也兴冲冲的这样做,然后卡了好久发现资料一直过不去,直到上网一查才发现,watchOS>2之后就不再支援此方法了….

要使用新的WatchConnectivity方式让手机跟手表之间进行通讯(类似socket概念),iOS手机及手表watchOS两端都需要实做,我们写成singleton模式如下:

手机端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import WatchConnectivity

class WatchSessionManager: NSObject, WCSessionDelegate {
    @available(iOS 9.3, *)
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        //手机端session启用完成
    }
    
    func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
        //手机端接受到手表传回的UserInfo
    }
    
    func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
        //手机端接受到手表回传的Message
    }
    
    //另外还有didReceiveMessageData,didReceiveFile同样都是处理收到手表回传的资料
    //看你的资料传递接收需求决定要用哪个
    
    func sendUserInfo() {
        guard let validSession = self.validSession,validSession.isReachable else {
            return
        }
        
        if userDefaultsTransfer?.isTransferring == true {
            userDefaultsTransfer?.cancel()
        }
        
        var list:[String:Any] = [:]
        //将UserDefaults放入list....
        
        self.userDefaultsTransfer = validSession.transferUserInfo(list)
    }
    
    func sessionReachabilityDidChange(_ session: WCSession) {
        //与手表APP连接状态改变时(手表开启APP时/手表关闭APP时)
        sendUserInfo()
        //我是当状态改变,如为手表开启APP时就同步一次UserDefaults
    }
    
    func session(_ session: WCSession, didFinish userInfoTransfer: WCSessionUserInfoTransfer, error: Error?) {
        //完成同步UserDefaults(transferUserInfo)
    }
    
    func sessionDidBecomeInactive(_ session: WCSession) {
        
    }
    
    func sessionDidDeactivate(_ session: WCSession) {
        
    }
    
    static let sharedManager = WatchSessionManager()
    private override init() {
        super.init()
    }
    
    private let session: WCSession? = WCSession.isSupported() ? WCSession.default : nil
    private var validSession: WCSession? {
        if let session = session, session.isPaired && session.isWatchAppInstalled {
            return session
        }
        //回传有效且连接中且手表APP开启中的session
        return nil
    }
    
    func startSession() {
        session?.delegate = self
        session?.activate()
    }
}

WatchConnectivity 手机端的 Code

并在iOS/AppDelegate.swift的application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?)中加入WatchSessionManager.sharedManager.startSession( ) 以在启动手机APP后连接上session

手表端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import WatchConnectivity

class WatchSessionManager: NSObject, WCSessionDelegate {
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
    }
    
    func sessionReachabilityDidChange(_ session: WCSession) {
        guard session.isReachable else {
            return
        }
        
    }
    
    func session(_ session: WCSession, didFinish userInfoTransfer: WCSessionUserInfoTransfer, error: Error?) {
        
    }
    
    func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
        DispatchQueue.main.async {
            //UserDefaults:
            //print(userInfo)
        }
    }
    
    static let sharedManager = WatchSessionManager()
    private override init() {
        super.init()
    }
    
    private let session: WCSession? = WCSession.isSupported() ? WCSession.default : nil
    
    func startSession() {
        session?.delegate = self
        session?.activate()
    }
}

WatchConnectivity 手表端的 Code

并在WatchOS Extension/ExtensionDelegate.swift中的applicationDidFinishLaunching( ) 加入 WatchSessionManager.sharedManager.startSession( ) 以在启动手表APP后连接上session

WatchConnectivity 资料传递方式

传资料用:sendMessage,sendMessageData,transferUserInfo,transferFile 收资料用:didReceiveMessageData,didReceive,didReceiveMessage 两端传接收方法都ㄧ样

可以看到手表传资料到手机都通,但手机传资料到手表仅限手表APP开启中

watchOS推播处理

专案目录底下的PushNotificationPayload.apns这时就派上用场了,这是用来在模拟器上测试推播之用,在模拟器上部署Watch App target,安装完启动App就会收到一则以这个档案内容的推播,让开发者更容易测试推播功能.

如要修改/启用/停用 PushNotificationPayload.apns,请选择Target后Edit Scheme

如要修改/启用/停用 PushNotificationPayload.apns,请选择Target后Edit Scheme

watchOS 推播处理:

同iOS我们实做UNUserNotificationCenterDelegate,在watchOS中我们也实作一样的方法,在watchOS Extension/ExtensionDelegate.swift中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import WatchKit
import UserNotifications
import WatchConnectivity

class ExtensionDelegate: NSObject, WKExtensionDelegate, UNUserNotificationCenterDelegate {

    func applicationDidFinishLaunching() {
        
        WatchSessionManager.sharedManager.startSession() //前面提到的WatchConnectivity连线
      
        UNUserNotificationCenter.current().delegate = self //设定UNUserNotificationCenter delegate
        // Perform any final initialization of your application.
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.sound, .alert])
        //同iOS,此做法可让推播在APP前景时依然会显示
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        //点击推播时
        guard let info = response.notification.request.content.userInfo["aps"] as? NSDictionary,let alert = info["alert"] as? Dictionary<String,String>,let data = info["data"] as? Dictionary<String,String> else {
            completionHandler()
            return
        }
        
        //response.actionIdentifier可得点击事件Identifier
        //预设点击事件:UNNotificationDefaultActionIdentifier
        
        if alert["type"] == "new_ask") {
            WKExtension.shared().rootInterfaceController?.pushController(withName: "showDetail", context: 100)
            //取得目前root interface controller 并 push
        } else {
           //其他处理....
           //WKExtension.shared().rootInterfaceController?.presentController(withName: "", context: nil)
            
        }
        
        completionHandler()
    }
}

ExtensionDelegate.swift

watchOS 推播显示,分成三种:

  1. static: 预设推播显示方式

会同手机推播,这边手机端iOS有实做UNUserNotificationCenter.setNotificationCategories在通知下方增加按钮;Apple Watch预设亦然会出现

会同手机推播,这边手机端iOS有实做UNUserNotificationCenter.setNotificationCategories在通知下方增加按钮;Apple Watch预设亦然会出现

  1. dynamic:动态处理推播显示样式(重组内容、显示图片)

  2. interactive:watchOS ≥ 5 后支援,在dynamic的基础下再增加支援按钮

可在Interface.storyboard中的Static Notification Interface Controller Scene设定推播处理方式

可在Interface.storyboard中的Static Notification Interface Controller Scene设定推播处理方式

static没什么好说的,就是走预设的显示方式,这边先介绍dynamic,勾选「Has Dynamic Interface」后会出现「Dynamic Interface」可在此视图设计你自订的推播呈现方式(不能使用Button):

我的自订推播呈现设计

我的自订推播呈现设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import WatchKit
import Foundation
import UserNotifications

class NotificationController: WKUserNotificationInterfaceController {

    @IBOutlet var imageView: WKInterfaceImage!
    @IBOutlet var titleLabel: WKInterfaceLabel!
    @IBOutlet var contentLabel: WKInterfaceLabel!
    
    override init() {
        // Initialize variables here.
        super.init()
        self.setTitle("结婚吧") //设定右上方标题
        // Configure interface objects here.
    }

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }
    
    override func didReceive(_ notification: UNNotification) {
        
        if #available(watchOSApplicationExtension 5.0, *) {
            self.notificationActions = []
            //清除iOS实做的UNUserNotificationCenter.setNotificationCategories在通知下方增加的按钮
        }
        
        guard let info = notification.request.content.userInfo["aps"] as? NSDictionary,let alert = info["alert"] as? Dictionary<String,String> else {
            return
        }
        //推播资讯
        
        self.titleLabel.setText(alert["title"])
        self.contentLabel.setText(alert["body"])
        
        if #available(watchOSApplicationExtension 5.0, *) {
            if alert["type"] == "new_msg" {
              //如果是新讯息推播则在通知下方增加回复按钮
              self.notificationActions = [UNNotificationAction(identifier: "replyAction",title: "回复", options: [.foreground])]
            } else {
              //其他则增加查看按钮
              self.notificationActions = [UNNotificationAction(identifier: "openAction",title: "查看", options: [.foreground])]
            }
        }
        
        
        // This method is called when a notification needs to be presented.
        // Implement it if you use a dynamic notification interface.
        // Populate your dynamic notification interface as quickly as possible.
        
    }
}

程式部分,ㄧ样拉outlet到controller并实做功能

再来讲到interactive,同dynamic,只是能多加Button,能跟dynamic设同个Class控制程式;interactive我没有使用,因为我的按钮是用程式self.notificationActions加上去的,差异如下:

左使用interactive,右使用self.notificationActions

左使用interactive,右使用self.notificationActions

两个做法都需watchOS ≥ 5 支援.

使用self.notificationActions增加按钮则按钮事件处理由ExtensionDelegate中的 userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) 处理,并以identifier识别动作

选单功能?

在元件库中拉入Menu,再拉入选单项目Menu Item,再拉IBAction到程式控制

在元件库中拉入Menu,再拉入选单项目Menu Item,再拉IBAction到程式控制

在页面重压就会出现:

内容输入?

使用内建的presentTextInputController方法即可!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@IBAction func replyBtnClick() {
    guard let target = target else {
        return
    }
    
    self.presentTextInputController(withSuggestions: ["稍后回复您","谢谢","欢迎与我联络","好的","OK!"], allowedInputMode: WKTextInputMode.plain) { (results) in
        
        guard let results = results else {
            return
        }
        //有输入值时
        
        let txts = results.filter({ (txt) -> Bool in
            if let txt = txt as? String,txt != "" {
                return true
            } else {
                return false
            }
        }).map({ (txt) -> String in
            return txt as? String ?? ""
        })
        //预处理输入
        
        
        txts.forEach({ (txt) in
            print(txt)
        })
    }
}

总结

谢谢你看到这!辛苦了!

到这里文章已告一段落,大略提了一下UI排版、程式、推播、介面应用部分,有开发过iOS的上手真的很快,几乎差不多而且许多方法都做了简化使用起来更简洁,但能做的事确实也变少了(像是目前还不知道怎么针对Table做载入更多);目前能做的事确实很少,希望官方在未来能开放更多API给开发者使用❤️❤️❤️

MurMur:

Apple Watch App Target 部署到手表真的有够慢 — [Narcos](https://www.netflix.com/tw/title/80025172){:target="_blank"}

Apple Watch App Target 部署到手表真的有够慢 — Narcos

有任何问题及指教欢迎 与我联络


Buy me a beer

本文首次发表于 Medium (点击查看原始版本),由 ZMediumToMarkdown 提供自动转换与同步技术。

Improve this page on Github.

This post is licensed under CC BY 4.0 by the author.