24.    Xcode 9, iOS 11, Swift

Auguest 19, 2018
home

Contents

01. My Second Look at ios

02. One Label and One Button

03. Variable, func, ,Any & AnyObject, class, Optionals

       //----------        3.1  Variable ------------------------
       
             var age: Int
       
       - Unlike most of OO, for exampe, in Jave like public int age = 3
       
             var age: Int
             do {
                print(age)
             } catch {
                print("never reach here")
             }
         
       
       - the above code will not run
       - compiling error: constant 'age' used before being initialized
       - modify it by var age:Int = 3
         

       //----------        3.2  func ------------------------
       
       //     ---  syntax-1 ---
       // declare a func
       func div1(a:Int, b: Int) -> Int {
           return a /b
       }
       // call it
       let result1 = div1(a:10, b:2)
       print(result1)

       //     ---   syntax-2     using underscore ---
       // declare a func
       func div2(_ a:Int, _ b: Int) -> Int {
           return a /b
       }
       // call it
       let result2 = div2(10,2)  // classic look
       print(result2)
       

       //----------        3.3.1 class  basic-----------------
       // lab in Xcode, add a label and button, connect to the view controller
       //         label name is msg
       // outside class ViewController, define a class and init the properties
       
            class Dog{
                var name: String = ""
                var weight: Int = 0
            } 
       
       // in the handler for the button, add the code as below
       
            let dog1 = Dog()        // create an instance
            dog1.name = "Tairo"     // set properties
            dog1.weight = 32
                                    // output the data
            self.msg.text = " \(dog1.name)  ,  \(String(dog1.weight))"
       

       //----------        3.3.2 class  inheritance -----------------
       In the following apple link, you'll find the demo
       
            class MediaItem {
                var name: String
                init(name: String) {
                    self.name = name
                }
            }
            class Movie: MediaItem {
                var director: String
                init(name: String, director: String) {
                    self.director = director
                    super.init(name: name)
                }
            }
 
            class Song: MediaItem {
                var artist: String
                init(name: String, artist: String) {
                    self.artist = artist
                    super.init(name: name)
                }
            }

            let library = [
                Movie(name: "Casablanca", director: "Michael Curtiz"),
                Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
                Movie(name: "Citizen Kane", director: "Orson Welles"),
                Song(name: "The One And Only", artist: "Chesney Hawkes"),
                Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
            ]
         
            notes:
             * class Movie and Song are inherited from MediaItem.
             * All the classes use method init.
             
             * When creating a instance, key word new is not used.
             * and, its init method is used.
               
             *The type of library is [AnyObject].  

             comments:
             1. There is no lab here.
             2. Lab will be demo in 3.5.4 Optional for type cast.
             3. Here, it shows a model style in swift.
             4. The swift model class code can be in a different file.
             5. iOS uses MVC design pattern. It is a dialect.
       
       //----------        3.4  Any  -----------------
       class Product{}          // define a class
       let p1 = new Product()   // create an instance

       let a: [Any] = [1, 0.99, "hello", true, p1]

      
       //----------        3.5  Optional --------------
       
       - To being able to understand some code is important.
       - Sometimes, those code are provided, and you have to modify them.
       - In Swift, optional concept is quite unique.
       - There are two main types - variable with nil value,  variable type convertion. 
       - The related understanding helps.
       - Most times, in the Xcode editor, some warning or error icons show up, you can click the fix icons to modify the code
       

       - 3.5.1  unwrap and test nil
        
          * When some variable can not be empty, use optional.
          * Unwrap it, if the assignment is good, use the unwrapped data.
          * if it is bad, nothing happens.
        
        * test in Xcode - one label and one button
        
            let theUrl: String? = "https://xcloud.com/yz"
            //let theUrl: String? = nil

            if let temp = theUrl {      // unwrap and test nil
                print(theUrl)
                print(temp)
                self.result.text = theUrl
            } else {
                print(theUrl)
                print("because of nil, no update the label")
            }
        
        test result with data
          - wrapped data:     Optional("http://xcloud/yz")
          - unwrapped data:   http://xcloud/yz
          - the unwrapped data is used.
        test result without data
          - wrapped data:     nil
          - Nothing happens.

        - 3.5.2  Unconditional Unwrapping,  ! operator
        When you’re certain that an instance of Optional contains a value, 
        you can unconditionally unwrap the value by using the forced unwrap operator (postfix !). 
             
        
             let name: String! = "John Doe"
             self.result.text = name
        
        
            You can use it directly.
        

        - 3.5.3   Optional chain
        
        class Residence{
            var numOfRooms = 1
        }

        class Person {                            // Class Person uses class Residence
            var residence: Residence?
        }

        class ViewController: UIViewController {
            @IBOutlet weak var result: UILabel!
            @IBAction func test(_ sender: Any) {
                let johnResidence = Residence()
                johnResidence.numOfRooms = 4
        
                let john = Person()
                john.residence = johnResidence     // comment out this to test nil
        
                if let roomCount = john.residence?.numOfRooms {       // optional chain to test nil for residence
                    self.result.text = String(roomCount)              // if success, retrieve data
                } else {
                    print("Unable to retrieve the number of rooms")   // if fail, no data retrival
                }
            }
            ...
     }       
        

        - 3.5.4   Optional for type cast   as?   as!
        In 3.3.2 class inheritance, there are 3 classes 
                 - super class:  MediaItem
                 - child class:  Movie, song
           then, create a constant library: [MediaItem]
        The following code is to use the above setup to demo optioanl cast
        
            for item in library {
                if let movie = item as? Movie {
                    print("Movie: \(movie.name), dir. \(movie.director)")
                } else if let song = item as? Song {
                    print("Song: \(song.name), by \(song.artist)")
                }
            }
        
        * iterate each item in the array
        
        * downcast item from MediaItem to Movie
        * if succeed, assign it
        * if fail, do not assign
        
        * the same for Type song

        lab:
        * create a iOS project, in the storyboard, add a label, and a button
        * connect them to the files.
        * In ViewController.swift, ouside class ViewController,
              add code for class MeiaItem, Movie, Song
        * in the button's event handler method, add code as below:
        
            class MediaItem{...}
            class Movie: MediaItem{...}
            class Song: MediaItem{...}
            class ViewControler...{
                ...
                @IBAction func test(_ sender: Any){
                   let library = [
                      Movie(name: "...", director: "..."),
                      Song(name: "...", artist: "...),
                      ...]
                   for item in library {
                        if let movie = item as? Movie {
                            print("Movie: \(movie.name), dir. \(movie.director)")
                        } else if let song = item as? Song {
                            print("Song: \(song.name), by \(song.artist)")
                        }
                    }
        
         * the result is in the debug area.
         
         * for-in loop is used to use test this scenario for querying data with different child types.
         * to see the part in the middle of a chain can be casted or not.
         
            

04. TableView

        class ViewController: UIViewController, UITableViewDataSource {
            func tableView(_ tableView: UITableView, 
                           numberOfRowsInSection section: Int) -> Int {
                return 15
            }

            func tableView(_ tableView: UITableView, 
                           cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                let cell = UITableViewCell()
                cell.textLabel?.text = "row \(indexPath.row)"
                return cell
            }
            

-- step 4    contents in a cell

       -- step 4.1 imageView object      
            * prepare one image in 3 different sizes
                myImage.png      50 * 50
                myImage@2x.png   100 * 100
                myImage@3x.png   150 * 150
            * add them to the project
                - in the project navigator, open Assets.xcassets   
                - click plus icon, click New Image Set
                - Drag these 3 images from the finder onto the target locations
                - name the data set to myImage
            * render it by code
                no work in storyboard
                - in ViewController, method cellForRowAt
                - after a cell is created, add code as below:
                  cell.imageView?.image = UIImage(named: "myImage")
            * test
               For the cell in each row, the image is on the left.
        -- step 4.2 accessoryType object
                cell.accessoryType = .disclosureIndicator //go to its detail screen
             
        -- step 4.3 detailTextLabel object
                cell.detailTextLabel?.text = "data for detailTextLabel"    // below the line for textLabel
        -- test: you can each cell display as below

             image icon       text         navigation icon
                          detail text
                
            

-- step 5    section,    indexPath's property

         -- step 5.1   numberOfSections
               * implementing a table view data source method(not required) 
               * if it is not implemented, the default is 1.
               * in ViewController.swift, inside the class, type the method name, the code intelliscence will complete the coding.
               * modify as:    return 3
               * test it to see 3 sections.

         -- step 5.2   numberOfRowsInSection
               * implementing a table view data source method(required) 
               * modify as below:
                    switch section {
                        case 0:
                            return 5
                        case 1:
                            return 2
                        case 3:
                            return 3
                        default:
                            return 0
                *test

            
         -- step 5.3   cellForRowAt
             * implementing a table view data source method(required) 
             * modify as below:

            func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                    let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath)
                    switch indexPath.section {
                        case 0:
                            cell.imageView?.image = UIImage(named: "myImage")
                            cell.textLabel?.text = "row \(indexPath.row)"
                            cell.accessoryType = .disclosureIndicator
                            cell.detailTextLabel?.text = "detailTextLabel for section 1"
                        case 1:    ....
                        case 2:    ....
                        default:
                            cell.textLabel?.text = "error xyz"
                    }
                return cell
            }
              * test

            -- step 5.4   titleForHeaderInSection
               * implementing a table view data source method(required) 
               * modify as below:
                    switch section {
                        case 0:
                            return "Section 1"
                        case 1:
                            return "Section 2"
                        case 3:
                            return "section 3"
                        default:
                            return nil
                *test
   
            

TableView - Data and protocal UITableViewDataSource

        class ViewController....        

        var breakfastMenu = [
                ["name": "oatmeal, eggs, coffee", "price": 12.50],
                ["name": "toast, fruits, tea", "price": 10.50],
                ["name": "French toast, scramlbe eggs, coffee", "price": 14.50],
                ["name": "pancake, porch eggs, coffee", "price": 13.50],
                ["name": "oatmeal, coffee", "price": 9.50],
            ]
            var lunchMenu = [
                ["name": "Salad, coffee", "price": 10.50],
                ["name": "burger", "price": 11.50],
                ["name": "French chicken", "price": 15.50]
            ]
            var dinnerMenu = [
                ["name": "Salmon", "price": 15.50],
                ["name": "clam chowder", "price": 12.50],
                ["name": "Onion soup", "price": 8.50]
            ]
            
            -- method numberOfRowsInSection,   using data as below:
            switch section {
                case 0:
                    return breakfastMenu.count
                case 1:
                    return lunchMenu.count
                case 2:
                    return dinnerMenu.count
                ....

            -- method cellForRowAt, using data as below
            switch indexPath.section {
            case 0:
                let name1 = breakfastMenu[indexPath.row]["name"] as! String
                row is for specific table view section for the view
                row is also for the corresponding data array.
                cell.textLabel?.text = name1
                ...
                let price1 = breakfastMenu[indexPath.row]["price"] as! Double
                cell.detailTextLabel?.text = String(price1)
            case 1:
                // lunchMenu
            case 2:
                // dinnerMenu
            

06. TableView - protocal UITableViewDelegate, and click a row

        print("You select section \(indexPath.section), row \(indexPath.row)")
            

07. TableView - Approaching data update

08. TableView - update lab setup



    import UIKit
    class ViewController: UIViewController,UITableViewDataSource {
  
        // 1. data   ---------------------------------------
        let amTasks = ["check weather", "plan todo", "walk dogs"]
        let pmTasks = ["do yard work", "study"]
        let eveningTasks = ["watch TV"]

        // 2. data source methods  -------------------------
        func numberOfSections(in tableView: UITableView) -> Int {
            return 3
        } 
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath)
        
            switch indexPath.section {
            case 0:
                cell.textLabel?.text = amTasks[indexPath.row]
            case 1:
                cell.textLabel?.text = pmTasks[indexPath.row]
            case 2:
                cell.textLabel?.text = eveningTasks[indexPath.row]
            default:
                cell.textLabel?.text = "error in cellForRowAt"
            }
            return cell
        }
   
        // not required 
        func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
            switch section {
            case 0:
                return "Morning Task"
            case 1:
                return "Afternoon Tasks"
            case 2:
                return "Evening Tasks"
            default:
                return nil
            }
        }

        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            switch section {
            case 0:
                return amTasks.count
            case 1:
                return pmTasks.count
            case 2:
                return eveningTasks.count
            default:
                return 0
            }
        }


    
        // 3. UIViewController methods  --------------------
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
        }

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




The above is the setup for ALL the following labs.

09. TableView - Removing a Row

            // ---------------    code is as below:  ------------
            func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
                guard editingStyle == .delete else { return }
                print("-----delete ------")
                
                switch indexPath.section {
                case 0:
                    amTasks.remove(at:indexPath.row)
                case 1:
                    pmTasks.remove(at:indexPath.row)
                case 2:
                    eveningTasks.remove(at:indexPath.row)
                default:
                    print("error in remove a row")
                }   
                tableView.deleteRows(at: [indexPath], with: .automatic)

            }
            

10. TableView - Adding a Row

        // code as below
        override func viewDidLoad() {
            super.viewDidLoad()
            self.navigationController?.navigationBar.barTintColor = UIColor.green
            self.title = "My Todolist"
            let addbutton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addRow))
            self.navigationItem.rightBarButtonItem = addbutton
        }

        @objc func addRow(){
            // 1  get a new row from a user's input

            // 1.1  create an alert
            let alert = UIAlertController(title: "enter a new task info, 0-based",
                        message: "task§ion_no&row_no",
                        preferredStyle: .alert)
                    
            // 1.2  add a UITextField
            alert.addTextField { (taskField: UITextField) in
                taskField.placeholder = "ig.  cook&1&1  for pm and 2nd row"
            }

            // 1.3  add a UIAlertAction
            alert.addAction(UIAlertAction(title: "OK", style: .default, 
             
               
               handler: {(action:UIAlertAction) in
                if let taskField = alert.textFields?.first {
                    if taskField.text == "" {
                        print("You have to enter a task")
                    } else {
                        print("task is \(taskField.text ?? "new task")")
                        // step 2:  process the input data - task description, insert location within the same scope
                        self.insertOneRow(inputRowData: taskField.text!)
                    }
                }
              })
              
            )
            self.present(alert, animated: true, completion: nil)
        }

        func insertOneRow(inputRowData: String){
            // step 2.1: parse the input data
            print("inputRowData = \(inputRowData)")
            let inArray = inputRowData.split(separator: "&")
            let task = inArray[0]
            let sectionNo = Int(inArray[1])
            let rowNo = Int(inArray[2])
            print("task = \(task)")
            print("sectionNo = \(sectionNo ?? 0)")
            print("rowNo = \(rowNo ?? 0)")

            // step 2.2: updata data
            switch sectionNo {
            case 0:
                amTasks.insert(String(task), at: rowNo!)
                print(amTasks)
            case 1:
                pmTasks.insert(String(task), at: rowNo!)
                print(pmTasks)
            case 2:
                eveningTasks.insert(String(task), at: rowNo!)
                print(eveningTasks)
            default:
                print("error in updata data")
            }

            // step 2.3: refresh view
            tableView.reloadData()
        }
            

11. TableView - Persisting data



----------------- steps for UserDefault as storage -------------------

        // call save for adding a row
        alert.addAction(...
             ....
             self.insertOneRow(....)
             self.save()
        )

        // call save for deleting a row
        // in the method for commit
        // after remove method
            self.save()

        // call load
        // add at the end.
        self.load()

            
        func save(){
            UserDefaults.standard.set(eveningTasks, forKey: "eveTasks")
            UserDefaults.standard.synchronize()
        }

        func load(){
            if let loadData = UserDefaults.standard.value(forKey: "eveTasks")
                                         as? [String] {
               eveningTasks = loadData
               tableView.reloadData()
            }
        }
            

----------------- steps for file as storage --------------------------

    // code for defining the full path
    override func viewDidLoad(){
        ...
        
        let docsDir = NSSearchPathForDirectoriesInDomains(.documentDirectory, .allDomainsMask, true)
        fileWithFullPath = docsDir[0].appending("eveningTasks.txt")
        

        load()
    }

    // code for save
    func save() {
        let newData:NSArray = NSArray(array: eveningTasks)
        newData.write(toFile: fileWithFullPath, atomically: true)
    }

    // code for load
    func load() {
        print("path = \(fileWithFullPath)")
        if let loadedData = NSArray(contentsOfFile:fileWithFullPath) as? [String] {
            evenTasks = loadedData
            tableView.reloadData()
        }
    }
        

----------------- steps for preparing data file -------------------