prettyprint

2015年9月9日 星期三

第 5 個程式:Table View 中 Cell 的刪除 1


要刪除 Table View Cell 的資料,該如何做? 其執行步驟如下:

1. 開啟 Table View 的 Data Model 為 Edit 編輯模式
2. 在此模式下刪除資料
3. UI 畫面重整 Refresh

Table View Cell 的資料來源依據 MVC (Model-View-Controller) 這架構,其來源是由其協定 UITableViewDataSource 實作。參考 UITableViewDataSource [1] 文件,其提供下列方法:

方法 tableView(_:commitEditingStyle:forRowAtIndexPath:) 會將此 Data Model 切換成 Edit 編輯模式,讓我們可以修改資料,此方法詳細內容如下:
由以上說明得知: 當開啟此 Edit 編輯模式之後,除 insertion (green plus) control 外,亦提供 deletion (red minus) control 用來刪除 Cell 上的資料。 我們使用 func tableView(_ tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) 這方法來開啟成 Edit 模式。然後,在此模式下實作我們要刪除的資料。

    override func tableView(tableView: UITableView,
        commitEditingStyle editingStyle: UITableViewCellEditingStyle,
        forRowAtIndexPath indexPath: NSIndexPath) {
            
            if editingStyle == .Delete {
                self.vocabulary.removeAtIndex(indexPath.row)
            }
    }
當資料刪除後,我們必須讓 UI 畫面重整 Refresh。UITableView 提供一個方法 reloadData() 來 refresh UI:

    override func tableView(tableView: UITableView,
        commitEditingStyle editingStyle: UITableViewCellEditingStyle,
        forRowAtIndexPath indexPath: NSIndexPath) {
            
            if editingStyle == .Delete {
                self.vocabulary.removeAtIndex(indexPath.row)
                self.tableView.reloadData()
            }
    }

驗證: 在模擬器上,選取一行資料 Cell,然後左滑:
此時這行的右邊會顯示 Delete 刪除這按鈕。當我們點選這按鈕後,這行會被刪除嗎?
Yeah! 這行真的被刪除了!這下可以鬆口氣! 後記: 這整個步驟是參考書中的練習,以及 Apple 的網站,先由一個該怎麼做的概念去進行,把該有的 code 準備好後,才發現原來 TableViewController 這 Template 很貼心幫我們準備好 code 如下,但貼上自己寫的,整個觀念會更清楚。

  /*
    // Override to support editing the table view.
    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        if editingStyle == .Delete {
            // Delete the row from the data source
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        } else if editingStyle == .Insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
        }    
    }
   */
附記: 程式碼:

//
//  MyTableViewController.swift
//  SimpleTableViewControllerDemo
//
//  Created by Elvis Meng on 2015/9/6.
//  Copyright (c) 2015年 Elvis Meng. All rights reserved.
//

import UIKit

class MyTableViewController: UITableViewController, UITableViewDataSource, UITableViewDelegate {
    
    var vocabulary = ["book","pen","queen","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15"]

    override func viewDidLoad() {
        super.viewDidLoad()

        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false

        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem()
        
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: - Table view data source

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        // #warning Potentially incomplete method implementation.
        // Return the number of sections.
        return 1
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete method implementation.
        // Return the number of rows in the section.
        return self.vocabulary.count
    }
   
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        
        let cellIdentifier = "Cell"
        let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! UITableViewCell

        // Configure the cell...
        cell.textLabel?.text = vocabulary[indexPath.row]
        
        return cell
    }

    override func tableView(tableView: UITableView,
        didSelectRowAtIndexPath indexPath: NSIndexPath) {
            
            let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
            if cell.accessoryType == UITableViewCellAccessoryType.None {
                cell.accessoryType = UITableViewCellAccessoryType.Checkmark
            } else {
                cell.accessoryType = UITableViewCellAccessoryType.None
            }
            tableView.deselectRowAtIndexPath(indexPath, animated: true)
    }
    
    override func tableView(tableView: UITableView,
        didDeselectRowAtIndexPath indexPath: NSIndexPath) {
            let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
            if cell.accessoryType == UITableViewCellAccessoryType.None {
                cell.accessoryType = UITableViewCellAccessoryType.Checkmark
            } else {
                cell.accessoryType = UITableViewCellAccessoryType.None
            }
            tableView.deselectRowAtIndexPath(indexPath, animated: false)
    }

    override func tableView(tableView: UITableView,
        commitEditingStyle editingStyle: UITableViewCellEditingStyle,
        forRowAtIndexPath indexPath: NSIndexPath) {
            
            if editingStyle == .Delete {
                self.vocabulary.removeAtIndex(indexPath.row)
                self.tableView.reloadData()
            }
    }

  /*
    // Override to support editing the table view.
    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        if editingStyle == .Delete {
            // Delete the row from the data source
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        } else if editingStyle == .Insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
        }    
    }
   */

    /*
    // Override to support rearranging the table view.
    override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {

    }
    */

    /*
    // Override to support conditional rearranging of the table view.
    override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        // Return NO if you do not want the item to be re-orderable.
        return true
    }
    */

    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        // Get the new view controller using [segue destinationViewController].
        // Pass the selected object to the new view controller.
    }
    */

}

參考: 1. UITableViewDataSource Protocol Reference, https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UITableViewDataSource_Protocol/ 2. UITableView Class Reference, https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UITableView_Class/index.html#//apple_ref/occ/cl/UITableView

2015年9月8日 星期二

第 4 個程式:Table View 中 Cell 的選取


協定 UITableViewDelegate 關於選取Selection 的 被選 / 取消需要實作的函數包含下列:

例如: 假設當 User 點選 Table View 上的 Cell 之後,系統會有何反應? 我們可在 talbeView(_: didSelectionAtIndexPath:) 此函數內寫 code,實作我們要的功能。
即,我們接下來要針對以下定義,實作其內容:

option func tableView(_ tableView: UITableView, 
                         didSelectRowAtIndexPath indexPath: NSIndexPath)


    override func tableView(tableView: UITableView,
        didSelectRowAtIndexPath indexPath: NSIndexPath) {
            
            let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
            if cell.accessoryType == UITableViewCellAccessoryType.None {
                cell.accessoryType = UITableViewCellAccessoryType.Checkmark
            } else {
                cell.accessoryType = UITableViewCellAccessoryType.None
            }
            tableView.deselectRowAtIndexPath(indexPath, animated: true)
    }

相反地,若我們希望取消選取呢?


    override func tableView(tableView: UITableView,
        didDeselectRowAtIndexPath indexPath: NSIndexPath) {
            let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
            if cell.accessoryType == UITableViewCellAccessoryType.None {
                cell.accessoryType = UITableViewCellAccessoryType.Checkmark
            } else {
                cell.accessoryType = UITableViewCellAccessoryType.None
            }
            tableView.deselectRowAtIndexPath(indexPath, animated: false)
    }
最後我來來驗證結果:
後記: 表格 Table View Cell 的選取與取消相當實用。很多書籍另設一個 Boolean 變數,來紀錄判別 Cell 的狀態,而不實作 tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath)。這樣有些缺乏 iOS 的精神。在反覆思考測試後,看到結果與自己預期,這喜悅難以言喻。 附註: 程式碼:MyTableViewController.swift

//
//  MyTableViewController.swift
//  SimpleTableViewControllerDemo
//
//  Created by Elvis Meng on 2015/9/6.
//  Copyright (c) 2015年 Elvis Meng. All rights reserved.
//

import UIKit

class MyTableViewController: UITableViewController, UITableViewDataSource, UITableViewDelegate {
    
    var vocabulary = ["book","pen","queen"]

    override func viewDidLoad() {
        super.viewDidLoad()

        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false

        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem()
        
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: - Table view data source

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        // #warning Potentially incomplete method implementation.
        // Return the number of sections.
        return 1
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete method implementation.
        // Return the number of rows in the section.
        return self.vocabulary.count
    }
   
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        
        let cellIdentifier = "Cell"
        let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! UITableViewCell

        // Configure the cell...
        cell.textLabel?.text = vocabulary[indexPath.row]
        
        return cell
    }

    override func tableView(tableView: UITableView,
        didSelectRowAtIndexPath indexPath: NSIndexPath) {
            
            let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
            if cell.accessoryType == UITableViewCellAccessoryType.None {
                cell.accessoryType = UITableViewCellAccessoryType.Checkmark
            } else {
                cell.accessoryType = UITableViewCellAccessoryType.None
            }
            tableView.deselectRowAtIndexPath(indexPath, animated: true)
    }
    
    override func tableView(tableView: UITableView,
        didDeselectRowAtIndexPath indexPath: NSIndexPath) {
            let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
            if cell.accessoryType == UITableViewCellAccessoryType.None {
                cell.accessoryType = UITableViewCellAccessoryType.Checkmark
            } else {
                cell.accessoryType = UITableViewCellAccessoryType.None
            }
            tableView.deselectRowAtIndexPath(indexPath, animated: false)
    }

    /*
    // Override to support editing the table view.
    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        if editingStyle == .Delete {
            // Delete the row from the data source
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        } else if editingStyle == .Insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
        }    
    }
    */

    /*
    // Override to support rearranging the table view.
    override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {

    }
    */

    /*
    // Override to support conditional rearranging of the table view.
    override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        // Return NO if you do not want the item to be re-orderable.
        return true
    }
    */

    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        // Get the new view controller using [segue destinationViewController].
        // Pass the selected object to the new view controller.
    }
    */

}
參考: 1. UITableViewDelegate, https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UITableViewDelegate_Protocol/ 2. UITableViewCell, https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UITableViewCell_Class/ 3. http://www.fanli7.net/a/bianchengyuyan/C__/20141215/491181.html 4. iOS Swift Tutorial - Building a Checklists App Part 1b, http://blog.mayankkapoor.com/post/89993926313/ios-swift-tutorial-building-a-checklists-app

2015年9月7日 星期一

製作 Image 的圖變成圓形


將 Image 的圖形由方塊變成圓形時,直覺上會認為由 UIImageView / UIImage,找到其相關的 Attribute 修改。但,事實上這 Attribute 已定義在其 Parent Class 父類別 UIView 中 [1]:

同樣,Image 的 layer 圖層這 Attribute 也被定義在父類別 UIView 中:
若我們要製作圓形的 Image,就要修改 CALayer [2] 這類別下的 cornerRadius 屬性:
由此知道, cornerRaidus 的資料型態 data type 被宣告為 CGFloat (浮點數),但若要製作 Image 為圓形,那其值又該如何設定? 此時理論上我們也會誤以為 Image 大小的屬性會在 UIImageView / UIImage 中宣告,但,這屬性亦在其父類別 UIView 中宣告:
而此 frame 屬性被宣告為:
接著我們往下查看 CGRect 這類別的定義:
再查看 CGSize [5] 的定義:
由此我們知道,要製作圓形的 Image,首先我們要指定 cornerRadius 的值,而這值是由我們現有 Image 的尺寸,由其中心點,畫成圓圈計算得來:

cell.myImage.layer.cornerRadius = cell.myImage.frame.size.width / 2
設定完之後,我們將邊角切掉:

cell.myImage.clipsToBounds = true
這樣圓形的 Image 就製作完成。 後記: 看來很簡單的製作,卻拉雜循序漸進寫了一堆! 若能舉一反三,碰到新的 UI Control 都能循著這思路,把 App 做出來。市面上的書大都只把這 2 行程式碼寫出,就算交代清楚,但怎麼來,對若是初入門的人而言,既使照書上做,頂多也只是學到打字。學 App 製作,不就是要學會如何釣魚,而不是只懂的吃魚嗎? 不過,每本書都各有其優點,都是作者辛苦的精華,在此僅做個補充。 參考: 1. UIView - ClipsToBounds , https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIView_Class/#//apple_ref/occ/instp/UIView/clipsToBounds 2. CALayer, https://developer.apple.com/library/prerelease/ios/documentation/GraphicsImaging/Reference/CALayer_class/index.html#//apple_ref/swift/cl/c:objc(cs)CALayer 3. UIImageView, https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIImageView_Class/ 4. CGRect, https://developer.apple.com/library/prerelease/ios/documentation/GraphicsImaging/Reference/CGGeometry/index.html#//apple_ref/swift/struct/c:@S@CGRect 5. CGSize, https://developer.apple.com/library/prerelease/ios/documentation/GraphicsImaging/Reference/CGGeometry/index.html#//apple_ref/c/tdef/CGRect

2015年9月6日 星期日

第 3 個程式: UITableViewController


這裡將 UITableView 改寫為 UITableViewController。此程式需要下列步驟:

1. 刪除 UI 元件 (ViewController)與其相關的 Code (ViewController.swift),這些都是新增 project 的 default 設定。將這些檔案 Move to Trash
2. 新增 UI 元件:拖曳 TableVewController 元件至 Storyboard
3. 指定初始的 Controller : 設定 Is Initial ViewController
4. 新增 Code : 新增一個自訂的 Class
5. 連接 UI 與 Code : 將 UI 的 View Controller 的屬性(Attribute) class 被指定此新的 class
6. 設定 UI 的 Attributes : style = Basic, Identifier = Cell
7. 新增 data source : 提供 Table View 要顯示的資料
8. 檢查 Child Class 除繼承 Parent Class 外,是否遵循 data source 與 delegate 協定
9. 覆寫 overwrite 此 data source 協定的 function
10. 試 Run

新增專案:

1. 刪除 UI 元件 (ViewController)與其相關的 Code (ViewController.swift),這些都是新增 project 的 default 設定。將這些檔案 Move to Trash
若新增一個 Storyboard,其 UI 編輯器沒有任何 UI 元件:
2. 新增 UI 元件:拖曳 TableVewController 元件至 Storyboard 為方便 UI 顯示,指定此專案針對 iPhone 開發,可先關掉 Use Size Class 選項。 即點選 View > Utilities > Show File Inspector , 然後不勾選 User Size Class:
接下來,搜尋 TableViewController:
接著拖曳 TableViewController 元件至 Storyboard:
3. 指定初始的 Controller : 設定 Is Initial ViewController
4. 新增 Code : 新增一個自訂的 TableViewController Class 新增 Cocoa Class 檔案:
設定此新增 Class 的屬性:
此時這新 Class 被新增:
5. 連接 UI 與 Code : 將 UI 的 View Controller 的屬性(Attribute) class 被指定此新的 class
6. 設定 UI 的 Attributes : style = Basic, Identifier = Cell
7. 新增 data source : 提供 Table View 要顯示的資料 8. 檢查 Child Class 除繼承 Parent Class 外,是否遵循 data source 與 delegate 協定
9. 覆寫 overwrite 此 data source 協定的 function 複寫 numberOfRowsInSection:
覆寫 cellForRowAtIndexPath:
覆寫 numberOfSectionsInTableView:
10. 試 Run
後記: Table View 可用 UITableView 實作,亦可用 UITableViewController 實作,兩者皆能製作出我們所要的結果。 iPhone App 的開發,除要熟習 Xcode 的操作之外,另外整個開發的 Flow 要清楚。例如:UI 的建立,Code 的建立,而這兩者之間又該如何建立關係等等。學開發先不求快,但要把每行程式都看懂,看得透徹,明白為何有這行 Code,那將來在開發時,就能得心應手,應用自如了。

Error: Could not find a storyboard named 'Main' in bundle NSBundle


Problem:

刪除 Main.Storyboard 這 default 設定,之後新增一個 Storyboard 為 MyStoryboard,在測試 Run 時,系統出現下列錯誤:

Study: 檢查之前設定,發現其仍沿用 default 的設定 Main:
Solution: 將 Storyboard 的值更改為 MyStoryboard 即可:

2015年9月5日 星期六

第 2 個程式:SimpleTableView


專案步驟:

1. UI TableView 元件製作:拖曳 UITableView Control 至 Storyboard
2. 設定 Table View 的 Attribute : prototype cell = 1 (原來的值是 0)
3. 設定 Table View 的 Cell Style : Basic
4. 設定 cell 的識別名稱 Identifier = cell
5. 試 Run
6. 在 code 中加入 Table View 需遵循的 protocol : 
   class ViewController : UIViewController, UITableViewDataSource, 
                          UITableViewDelegate
7. Data 準備 :提供 data
8. Protocol 實作 : delegate
9. 建立 UI 與 Code 的 connect : 將 TableView 此 control 拉至 Outline 的  ViewController
10. 試 Run
11. 刪除 Status Bar
12. 試 Run

開啟新專案:

1. UI TableView 元件製作:拖曳 UITableView Control 至 Storyboard
2. 設定 Table View 的 Attribute : prototype cell = 1 (原來的值是 0)
3. 設定 Table View 的 Cell Style : Basic
4. 設定 cell 的識別名稱 Identifier = cell
5. 試 Run
6. 在 code 中加入 Table View 需遵循的 protocol : class ViewController : UIViewController, UITableViewDataSource, UITableViewDelegate
7. Data 準備 :提供 data 8. Protocol 實作 : delegate
9. 建立 UI 與 Code 的 connect : 將 TableView 此元件拉至 Outline 的 ViewController
顯示 connect to data source 與 data delegate
10. 試 Run
11. 刪除 Status Bar
12. 試 Run
後記: 這程式使用 UITableView 元件,增加 protocol 協定的概念。當遵循此 protocol 時,其方法需實作。 參考: 1. UITableView, https://developer.apple.com/library/prerelease/ios/samplecode/TableMultiSelect/Introduction/Intro.html 2. UITableView Class Reference, https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UITableView_Class/ 3. UITableViewDataSource Class Reference, https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UITableViewDataSource_Protocol/index.html#//apple_ref/occ/intf/UITableViewDataSource
4. UITableViewDelegate Reference Class, https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UITableViewDelegate_Protocol/index.html#//apple_ref/occ/intf/UITableViewDelegate
prettyPrint();