因?yàn)楣拘枨笮四啵宰隽藗€(gè)TodayExtension,關(guān)于蘋果從iOS8之后開始提供的Extension系列在此就不多加描述了虾宇,本篇主要是對自己這幾天制作TodayExtension的總結(jié)以及希望能為一部分不熟悉此道的朋友提供一點(diǎn)借鑒搓彻。
另外有誰了解
func widgetPerformUpdateWithCompletionHandler(completionHandler: (NCUpdateResult) -> Void) {
<#code#>
}
的調(diào)用機(jī)制嗎?我一直不是很明白嘱朽。
因?yàn)槟壳熬吐毜墓緲I(yè)務(wù)與Twitch相類似旭贬,因此TodayExtension的樣式也在盡可能地與他們相接近。
在真正開始動(dòng)手前我們需要先對Twitch的TodayExtension樣式進(jìn)行剖析搪泳,這是最重要的工作稀轨,如果不在一開始就對自己要做的事做出一定程度的規(guī)劃,當(dāng)工作進(jìn)行到一半時(shí)發(fā)現(xiàn)有地方不對勁那就為時(shí)已晚岸军。
Twitch的TodayExtension是這樣的:
通過截圖我們可以大致明白它的構(gòu)造奋刽,Extension的主要結(jié)構(gòu)是UITableView,占核心地位的游戲直播內(nèi)容由三個(gè)TableViewCell構(gòu)成,其上則是未登錄狀態(tài)的登錄提示艰赞,這個(gè)可以用TableViewHeaderView解決佣谐,其下是顯示更多的Button,第一次點(diǎn)擊時(shí)增加一行TableViewCell方妖,同時(shí)修改ButtonTitle狭魂,第二次點(diǎn)擊時(shí)進(jìn)入主App,這個(gè)同樣可以用TableViewFootView來完成党觅,當(dāng)然雌澄,如果你有更好的選項(xiàng)也可以使用其他選項(xiàng)來完成,編程就是這樣杯瞻,你永遠(yuǎn)不會(huì)只有一條可選路镐牺,除非你在跟別人討論什么是最好的語言。
那么來干吧魁莉,首先我們需要一個(gè)TodayExtension,你可以通過File->New->Target->Application Extension來得到它任柜。
我不打算說太多關(guān)于TodayExtension的基礎(chǔ)知識,主要原因是我懶得截那么多張圖以及干別人干過的事沛厨。
所以宙地,在很久很久以后,我們擁有一個(gè)一片荒蕪的TodayExtension以及一個(gè)AppGroups,雖然我們絕大多數(shù)展示任務(wù)都是使用網(wǎng)絡(luò)請求來完成的逆皮,但網(wǎng)絡(luò)請求需要的Key還是得從主程序獲得宅粥,當(dāng)然,你們也可以用一次骯臟的PY交易請求后臺給你一個(gè)不需要任何Key的接口电谣。
override func viewDidLoad() {
super.viewDidLoad()
//這個(gè)屬性用來設(shè)置你的TodayExtension大小秽梅,你可以隨時(shí)對它進(jìn)行更改,但當(dāng)preferredContentSize = CGSizeMake(0, 0)時(shí)似乎是無效的
preferredContentSize = CGSizeMake(0, 1)
//首先判斷是否有從主程序傳輸過來的數(shù)據(jù)抹蚀,若不存在則認(rèn)為是未登錄狀態(tài),這里跟Twitch有點(diǎn)差別企垦,我們未登錄狀態(tài)不顯示數(shù)據(jù)
guard let dict = NSUserDefaults(suiteName: "group.XXXToday")?.dictionaryForKey("Key") else {
isLogIn = false
preferredContentSize = CGSizeMake(0, footViewHeight)
setupTableView()
return
}
user = User(dict: dict)
setupTableView()
setupNeetWork()
}
//TableView以及自定義Cell使用StoryBoard搭建环壤,當(dāng)然,你也可以使用純代碼钞诡,這并無區(qū)別.
func setupTableView() {
tableView.delegate = self
tableView.dataSource = self
tableView.registerNib(UINib(nibName:"TodayTableViewCell",bundle: nil), forCellReuseIdentifier: Identifier)
tableView.separatorStyle = .SingleLine
tableView.rowHeight = rowHeight
addFootView()
}
func addFootView() {
if let footView = NSBundle.mainBundle().loadNibNamed("TodayFootView", owner: nil, options: nil).first as? TodayFootView {
footView.frame = CGRectMake(0, 0, tableView.frame.size.width, footViewHeight)
//通過一個(gè)計(jì)算性屬性根據(jù)不同狀態(tài)改變footView中ButtonTitle郑现,
if !isLogIn {
footView.isLogIn = isLogIn
}
//按鈕點(diǎn)擊回調(diào),在這里處理相關(guān)邏輯
footView.DidClick = { [weak self] (gohome) -> Void in
if let weakself = self {
if !weakself.isLogIn {
weakself.extensionContext?.openURL(weakself.getUrl(nil), completionHandler: nil)
} else {
//第二次點(diǎn)擊荧降,進(jìn)入主程序
if gohome {
weakself.extensionContext?.openURL(weakself.getUrl(nil), completionHandler: nil)
} else {
//第一次點(diǎn)擊接箫,增加一行Cell,刷新數(shù)據(jù)
weakself.dataCount = 4
weakself.preferredContentSize = CGSize(width: 0, height: weakself.rowHeight * CGFloat(weakself.dataCount) + weakself.footViewHeight + weakself.space * 2)
//Twitch有個(gè)延遲刷新的動(dòng)畫,具體效果我判斷為“先改變weakself.preferredContentSize朵诫,定時(shí)器延遲XX秒執(zhí)行刷新數(shù)據(jù)”
//當(dāng)然辛友,你也可以不這么做,直接刷新數(shù)據(jù)剪返,效果還是挺不錯(cuò)的.
NSTimer.scheduledTimerWithTimeInterval(0.5, target: weakself, selector: #selector(weakself.tableView.reloadData), userInfo: nil, repeats: false)
}
}
}
}
//我不確定是不是我操作有誤废累,直接將footView賦值給 tableView.tableFooterView會(huì)出現(xiàn)問題
tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: rowHeight))
tableView.tableFooterView?.addSubview(footView)
}
//如果你對TodayExtension自帶的空隙大小不甚滿意,可以通過這個(gè)方法來修改
func widgetMarginInsetsForProposedMarginInsets(defaultMarginInsets: UIEdgeInsets) -> UIEdgeInsets {
return UIEdgeInsetsMake(defaultMarginInsets.top, defaultMarginInsets.left, 0, defaultMarginInsets.right)
}
好了脱盲,這樣我們就大致完成Twitch的樣式了九默,我們有一個(gè)TableView,還弄了個(gè)按鈕在它的FootView上宾毒,我們還需要啥驼修?
對了,還需要數(shù)據(jù)诈铛,沒有數(shù)據(jù)的TableView除了那個(gè)可憐的按鈕外啥都沒有乙各,我們需要進(jìn)行一次網(wǎng)絡(luò)請求來獲取我們需要的數(shù)據(jù)展示出來。
但在此之前幢竹,我得說耳峦,盡量不要在Extension中使用第三方框架,太多的框架以及錯(cuò)綜復(fù)雜的依賴問題會(huì)讓Extension不堪負(fù)重焕毫,以及Extension并不完全與主程序相同蹲坷,除了更少的內(nèi)存額度外它也無法調(diào)用一些主程序中可以使用的API,例如UIApplication.sharedApplication(),如果你的第三方框架恰好有調(diào)用類似的API那你就不得不對第三方框架進(jìn)行刪改了,如果更恰好的是你使用的第三方框架中這種失效的API占主導(dǎo)地位邑飒,那樂子就大了循签。
func setupNeetWork() {
guard let url = NSURL(string: "\(TodayViewController.API_BASE_URL)/XXXX") else {
return
}
let request = NSMutableURLRequest(URL: url)
request.setValue(TodayViewController.XXXX, forHTTPHeaderField: "XXXXX")
NSURLSession.sharedSession().dataTaskWithRequest(request) { [weak self] (data, response, error) in
if error != nil || (response as! NSHTTPURLResponse).statusCode != 200 { return }
guard let data = data else { return }
guard let Array = try! NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments) as? [[String:AnyObject]] else { return }
var castArray = [Cast]()
for dict:[String:AnyObject] in Array {
let cast = Cast(dict: dict)
castArray.append(cast)
if castArray.count > 4 {
break
}
}
self?.castArray = castArray
self?.dataCount = 3
//使用NSURLSession千萬別忘記回主線程刷新數(shù)據(jù)
dispatch_async(dispatch_get_main_queue(), { [weak self] in
if let weakself = self {
weakself.tableView.reloadData()
weakself.preferredContentSize = CGSize(width: 0, height: weakself.rowHeight * CGFloat(weakself.dataCount) + weakself.footViewHeight + weakself.space)
}
})
}.resume()
}
一般來說,如此我們便已大致模仿出Twitch的樣式了疙咸,如圖:
剩下的便是細(xì)節(jié)問題县匠,比如說Twitch的按鈕:
普通的按鈕在點(diǎn)擊時(shí)并不改變背景色,并且若為高亮狀態(tài)時(shí)字的顏色(或者是Alpha)也會(huì)有相應(yīng)的變化,但Twitch的Button點(diǎn)擊時(shí)Button的背景色有相應(yīng)的變化乞旦,并且ButtonTitle依然清晰贼穆。
我在思考后決定如此實(shí)現(xiàn):
//首先監(jiān)聽按鈕的狀態(tài)變化,因?yàn)門odayExtension是獨(dú)立于主程序兰粉,直接寫UIButton的擴(kuò)展不會(huì)污染主程序的Button故痊,所以若要在主程序這么做最好寫成UIButton的子類。
extension UIButton {
public override var highlighted: Bool {
get {
return super.highlighted
}
set {
if newValue {
backgroundColor = UIColor.darkGrayColor()
} else {
backgroundColor = UIColor.lightGrayColor()
}
}
}
}
實(shí)現(xiàn)背景色的變化后該改ButtonTitle了玖姑。
如圖愕秫,我選擇了不設(shè)置Button的TitleLabel,而是拉了一個(gè)UILabel并讓它與Button同級客峭,這樣,在Button高亮的時(shí)候就不會(huì)影響到ButtonTitle了抡柿。(如果要完全模擬TwitchButton的效果的話記得設(shè)置Button.alpha,我設(shè)了0.5舔琅,效果大致與TwitchButton一致。)
但關(guān)于Button的優(yōu)化還沒有結(jié)束洲劣,我寫的Button相關(guān)代碼大致如下:
var goHome = false
var DidClick:((goHome:Bool)-> Void)?
//監(jiān)聽登錄狀態(tài)以設(shè)置不同的文本
var isLogIn = true {
didSet {
if isLogIn {
buttonLabel.text = "顯示更多..."
} else {
buttonLabel.text = "加入XXXX"
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
//圓角
todayButton.layer.cornerRadius = 3
todayButton.layer.masksToBounds = true
}
@IBAction func buttonDidClick(sender: UIButton) {
//當(dāng)點(diǎn)擊結(jié)束時(shí)瞬間改回原本的顏色备蚓,如果只監(jiān)聽狀態(tài)的話效果會(huì)慢上半拍
sender.backgroundColor = UIColor.lightGrayColor()
if isLogIn {
buttonLabel.text = "在XXXX顯示更多"
}
//閉包回調(diào)
DidClick?(goHome: goHome)
goHome = true
}
這樣的代碼平時(shí)已經(jīng)夠用了,但在TodayExtension中有個(gè)非常明顯的BUG囱稽,當(dāng)我按住Button沒有放開而是來回拖動(dòng)時(shí)可能會(huì)觸發(fā)Today界面的滑動(dòng)郊尝,此時(shí)Button的背景色便會(huì)一直是darkGrayColor(),除非再次點(diǎn)擊刷新它的背景色战惊,這樣的效果自然不符合我們的需求流昏,解決的辦法很簡單。
//執(zhí)行TouchCancelAction
@IBAction func buttonDidCancel(sender: UIButton) {
sender.backgroundColor = UIColor.lightGrayColor()
}
這樣效果就大致完畢了吞获。