iOS-模仿Twitch的TodayExtension

因?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是這樣的:

TwitchTodayExtension.png
![Uploading Demo_195000.png . . .]

通過截圖我們可以大致明白它的構(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的樣式了疙咸,如圖:


Demo.png

剩下的便是細(xì)節(jié)問題县匠,比如說Twitch的按鈕:

TwitchButton2.png

TwitchButton1.png

普通的按鈕在點(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了玖姑。

TwitchButton3.png

如圖愕秫,我選擇了不設(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()
    }

這樣效果就大致完畢了吞获。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末况凉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子各拷,更是在濱河造成了極大的恐慌刁绒,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烤黍,死亡現(xiàn)場離奇詭異知市,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)速蕊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門嫂丙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人规哲,你說我怎么就攤上這事奢入。” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵腥光,是天一觀的道長关顷。 經(jīng)常有香客問我,道長武福,這世上最難降的妖魔是什么议双? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮捉片,結(jié)果婚禮上平痰,老公的妹妹穿的比我還像新娘。我一直安慰自己伍纫,他們只是感情好宗雇,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著莹规,像睡著了一般赔蒲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上良漱,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天舞虱,我揣著相機(jī)與錄音,去河邊找鬼母市。 笑死矾兜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的患久。 我是一名探鬼主播椅寺,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蒋失!你這毒婦竟也來了配并?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤高镐,失蹤者是張志新(化名)和其女友劉穎溉旋,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嫉髓,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡观腊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了算行。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梧油。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖州邢,靈堂內(nèi)的尸體忽然破棺而出儡陨,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布骗村,位于F島的核電站嫌褪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏胚股。R本人自食惡果不足惜笼痛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望琅拌。 院中可真熱鬧缨伊,春花似錦、人聲如沸进宝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽党晋。三九已至谭胚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間隶校,已是汗流浹背漏益。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工蛹锰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留深胳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓铜犬,卻偏偏與公主長得像舞终,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子癣猾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,837評論 25 707
  • 他們不到覺悟的時(shí)候敛劝,就不會(huì)造反;他們不造反纷宇,他們就不會(huì)覺悟夸盟。所謂自由,就是可以說二加二等于四的自由像捶。你保持清醒的理...
    萬山如墨閱讀 1,089評論 0 2
  • 又是一年畢業(yè)季,時(shí)間過的真快硼莽,畢業(yè)整整一年庶溶,搖身一變成為下架學(xué)姐。最近朋友圈各種畢業(yè)照,想起了那時(shí)的我們偏螺,仔...
    班主任的課閱讀 1,544評論 0 0
  • 回想起小的時(shí)候自己行疏,是個(gè)人來瘋,小嘴兒就想討食兒吃的小鳥兒一樣嘰嘰喳喳沒完沒了砖茸,不管到哪里隘擎,不管認(rèn)識不認(rèn)識的都可以...
    幸福密碼_173f閱讀 204評論 0 0