prettyprint

2017年12月22日 星期五

我的 Hello, World


第一個簡單的 App 該怎麼寫?許多書都攏統的寫了一堆,最後 code 程式碼出現了,試跑後,一個完美的結果出現,這時我們學到了什麼?

好像教寫程式就是千篇一律這樣教法,這樣的寫法,能不能換個另外的方式?這樣的教法好像讓我想起了 Assembly 組合語言的年代,即程式語言一直在進步,但教法上依然逗留在石器時代。為何不這樣教?

我的 App 主要的目的用途是什麼?那我需要什麼樣的介面?然後,我又怎樣去實作這個 App 呢?

例如:我想寫一個 Hello, World 的 App 作為練習。那我最後 UI 的呈現方式可能有:

1. 在 iPhone 的畫面上只顯示 "Hello, World!"
2. 按下一個按鈕 Button,然後輸出一個文字字串 "Hello, World!"
3. 還是,按下一個按鈕後,會跳出視窗,視窗上顯示“Hello, World!" 這字串

針對第一個項目,若只是顯示"Hello, World!" 這字串,那在 UI 上我只要加入 Label 這UI Control 控制項即可。

既然 iPhone App 是物件導向設計 Objective Oriented Design,每個物件一定有屬性與方法。例如:Label 有個屬性 Text,而我們只要設定 Text 的值為 "Hello, Wold!" 就好。

若是第二個項目,我們的 UI 需要一個按鈕,點選 click 按鈕後,會將結果 “Hello, World!" 顯示出來。

點選按鈕,然後就顯示文字,這需要寫程式。但,寫完程式後,按鈕這個 UI 又怎麼跟程式 Code 有關聯呢?

最後若是選第三個項目,我們除了要知道第二個項目的運作細節,還要處理按下按鈕後,跳出的視窗細節:這視窗屬於那類的視窗?除了顯示 "Hello, World!" 這文字,如何結束關閉視窗?等等。

第三個項目包含相當多的實作觀念要了解。例如:我們選定跳出的視窗是 Alert 警告視窗。我們會用 UIAlertController 來實作一個 Alert 視窗物件。

為何要用 UIAlertController 呢?由 Apple 官網文件 UIAlertController 得知其繼承 Inheritance 自 UIViewController。 Controller 就像管理者,管理視窗 View 要怎麼呈現。

我們在新增 HelloWorld 這專案 Project 時,選定 Simple View App 這個模板 Template,系統的 default 就帶出一個簡單的 iPhone 畫面,這畫面在 code 程式碼   ViewController.swift 被定義為

class ViewController: UIViewController {
...
}

每個 View 都會由一個 Controller 去管理,這就是為何我們新增 Alert 這個視窗時,必須新增 Controller 的原因。

這看似簡單的第三個項目,實際上已經包含很多 OO (Objective Oriented) 概念及 iPhone App 的設計概念。對剛開始研讀就想一窺就裡的人負擔算是蠻大的。但,學中做,做中學,也是學習的一種方式。有些開發的精髓要隨著 Code 寫久的人才能領略。

PS.  Apple 除了提供 Playground 這互動工具外,另外一個很棒的程式語言測試工具,即 REPL (Read-Evaluate-Print-Loop),這工具在Terminal 環境下操作。

範例 1 :

新增專案後,在 Label 的 Text 屬性設定 "Hello, World!" 即可。



範例 2:

這次我們改用 Button 這個 UI Control,在其Text 屬性設定為 "Hello, World!" ,然後點選 Button 此新增按鈕,按下 Control 鍵不放,之後拖曳至 ViewController.swift,此時會跳出Connect 視窗,將 UI 與 Code 做個關連。


加入程式 print("Hello, World!") 如下:


點選 Hello, World! 這個按鈕後,會將字串 "Hello, World!" 輸出到命令列視窗 (Console)。

範例 3:

在這個範例中,我們實作下列程式:

class ViewController: UIViewController {

    @IBAction func showMessage(_ sender: UIButton) {
        //print("Hello, World!")
        let alert = UIAlertController(title: "My First App", message: "Hello,World!", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        self.present(alert, animated: true, completion: nil)
    }   

}

記得要在生成一個 Alert 物件後,要以 self.present() 顯示出來。



執行後,點選 OK 選項按鈕,此 Alert 會關閉。

範例 4:

在 Terminal 環境下,輸入 xcrun swift 指令,即進入 Swift 的 REPL 此互動環境:

要進入 Swift 環境,需要輸入下列指令:

$ sudo xcode-select -s /Applications/Xcode.app/Contents/Developer/

然後輸入:

xcrun swift




/end

開發iPhone App, 選擇 Swift 或 Objective-C?


Swift 語言優於 Objective-C,這爭議由Simon Ng 知名作家的"iOS 10 App 程式設計實力超進化實戰攻略“一書的範例可看出:

Objective-C

const int count = 10;
double price = 23.55;
NSString *firstMessage = @"Swift is awesome.";
NSString *secondMessage = @"What to you think?";
NSString *message = [NSString stringWithFormat:@"%@%@",firstMessage, secondMessage];
NSLog(@"%@",message);

Swift

let count = 10
var price = 23.55
let firstMessage = "Swift is awesome."
let secondMessage = "What to you think?"
let message = firstMessage + secondMessage
print(message)

哇!Swift 在語法Syntax上的表達簡潔而有力!

首先,每行程式 (Statement) 的結尾: Swift 不用分號(; semicolon) 來作為每行程式的結尾。這是 C/C++/C#/Java 慣用的寫法。

第二:常數與變數的宣告。在Swift的常數以 let 宣告,而變數則以 var 宣告。這點與 Objective-C 在常數宣告必須特別指定用 const 並無法說俗優俗劣。但,Swift 的常數或變數不需要特別定義其資料型態 (Data Type),而是Swift 可以根據其指定的值來推論 (Infer) count 是整數型態,而 price 是 double 資料型態。

當然 ,Swift 宣告整數或變數時,也可定義其 Data Type。例如:

let count : Int = 10
var price : Double  = 23.55

第三:字串的宣告簡潔。Swift 在字串(String)的宣告是直覺式的,僅以 firstMessage = "Swift is awesome." 表達。但,Objective-C 卻需用很繞口的符號來完成一件事。例如:

NSString *firstMessage = @"Swift is awesome.";

在字串前面用 * 符號 (*firstMessage) 對初學者而言會問:這是什麼玩意啊?殊不知這是 C 語言的餘孽。在 C 裏頭,符號 * 代表指針(pointer),所以 firstMessage 儲存的是指向 "Swift is awesome." 這一串值的位置。

讀到此,是不是有點頭痛?學 C 的功力好壞就看 Pointer 是否運用自如得體!

第四:字串的串接 (Concat) 上,Swift 是大大優於 Objective-C。後者對初學者而言看似用了奇怪的語法。2 個字串的串接在直覺上不是“第一個字串” 串接 (concat) "第二個字串“就好了嗎?至於串接(concat)這指令是用加號(+)或是其他符號都可。

字串的操作(插入/刪除/修改某字元/字串)在 Objective-C 較為複雜。被宣告為 NSString 的字串是常數字串,不能變更,必須透過 NSMutableString 宣告為變數字串才行。對 Swift 而言就沒有這麼麻煩,字串是否為常數或變數只是由 let 或 var 的宣告定義。

最後,在字串的顯示上,Objective-C 需定義顯示字串的格式 (print format),而 Swift 則由其編譯器去推論判斷。

結論:

Swift 的簡潔語法可大大增加程式的開發速度,同時在程式碼維護的易讀 (Readable) 也有加分的作用。用 Swift 編譯出的執行檔 executable code,根據官方說法,其執行效能又優於 Objective-C。Swift 這種種的優點讓程式開發者不心動也難 ?!

/end

2017年6月21日 星期三

[Swift 3] Error: unrecognized selector sent to instance

Problem



self.present(alertController, animated: true, completion: nil)


當 compiling 時,出現下列 error:

2017-06-21 11:04:57.574 HelloWorlDemo[2244:87318] -[HelloWorlDemo.ViewController helloWorldBtn:]: unrecognized selector sent to instance 0x7fc2d2e0f110
2017-06-21 11:04:57.586 HelloWorlDemo[2244:87318] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[HelloWorlDemo.ViewController helloWorldBtn:]: unrecognized selector sent to instance 0x7fc2d2e0f110'
*** First throw call stack:
(
0   CoreFoundation                      0x000000010ba65b0b __exceptionPreprocess + 171
1   libobjc.A.dylib                     0x0000000108df5141 objc_exception_throw + 48
2   CoreFoundation                      0x000000010bad5134 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
3   CoreFoundation                      0x000000010b9ec840 ___forwarding___ + 1024
4   CoreFoundation                      0x000000010b9ec3b8 _CF_forwarding_prep_0 + 120
5   UIKit                               0x00000001092c8d82 -[UIApplication sendAction:to:from:forEvent:] + 83
6   UIKit                               0x000000010944d5ac -[UIControl sendAction:to:forEvent:] + 67
7   UIKit                               0x000000010944d8c7 -[UIControl _sendActionsForEvents:withEvent:] + 450
8   UIKit                               0x000000010944c802 -[UIControl touchesEnded:withEvent:] + 618
9   UIKit                               0x00000001093367ea -[UIWindow _sendTouchesForEvent:] + 2707
10  UIKit                               0x0000000109337f00 -[UIWindow sendEvent:] + 4114
11  UIKit                               0x00000001092e4a84 -[UIApplication sendEvent:] + 352
12  UIKit                               0x0000000109ac85d4 __dispatchPreprocessedEventFromEventQueue + 2926
13  UIKit                               0x0000000109ac0532 __handleEventQueue + 1122
14  CoreFoundation                      0x000000010ba0bc01 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
15  CoreFoundation                      0x000000010b9f10cf __CFRunLoopDoSources0 + 527
16  CoreFoundation                      0x000000010b9f05ff __CFRunLoopRun + 911
17  CoreFoundation                      0x000000010b9f0016 CFRunLoopRunSpecific + 406
18  GraphicsServices                    0x000000010d970a24 GSEventRunModal + 62
19  UIKit                               0x00000001092c7134 UIApplicationMain + 159
20  HelloWorlDemo                       0x000000010881d9e7 main + 55
21  libdyld.dylib                       0x000000010ca0565d start + 1
22  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

(lldb) 

Solution


Swift 3.0 已改版,需將參數 animated 設定值為 false 即可:


self.present(alertController, animated: false, completion: nil)

2017年5月9日 星期二

範例:豆漿食譜

Objective

製作豆漿時,豆子與水的比例相當重要,此範例提供方便計算,而無需人為的換算

UI 

事實上,此 UI 僅提供簡單設計,重點在強調功能面。有關各 UI Component 日後再調整。



Code

//
//  ViewController.swift
//  FoodRecipeUnit
//
//  Created by Elvis Meng on 2017/5/8.
//  Copyright © 2017 Elvis Meng. All rights reserved.
//

import UIKit

class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
    
    var soyBeanUnit =  ["公克",""]
    var waterUnit = ["",""]
    var sugarUnit = ["公克",""]
    var brixUnit = ["微甜",""]
    
    var soyBeanUnitSelected = "公克"
    var waterUnitSelected = ""
    var sugarUnitSelected = "公克"
    var brixUnitSelected = "微甜"
    
    var soyBeanAmount = 0.0
    var waterAmount = 0.0
    var sugarAmount = 0.0
    
    @IBOutlet weak var soyBeanWeight: UITextField!
    @IBOutlet weak var waterWeight: UITextField!
    @IBOutlet weak var sugarWeight: UITextField!
    
    @IBAction func soyBeanEditOnExit(_ sender: UITextField) {
        
        if soyBeanUnitSelected == "" {
            soyBeanAmount = 600.0 * Double(soyBeanWeight.text!)!
        } else {
            soyBeanAmount = Double(soyBeanWeight.text!)!
        }
        
        if waterUnitSelected == "" {
            waterWeight.text = String(soyBeanAmount * 7.0 / 1500.0)
        } else {
            waterWeight.text = String(soyBeanAmount * 7.0 * 2500 / 1500.0)
        }
        
    }
    
    @IBAction func waterEditOnExit(_ sender: UITextField) {
        
        if waterUnitSelected == ""  {
            waterAmount = 2500.0 * Double(waterWeight.text!)!
        } else {
            waterAmount = Double(waterWeight.text!)!
        }
        
        if soyBeanUnitSelected == "" {
            soyBeanWeight.text = String(waterAmount * 2.5 / (7.0 * 2500.0))
            
        } else {
            soyBeanWeight.text = String(waterAmount * 2.5 / (7.0 * 2500.0) * 600.0)
        }
    }

    
    @IBAction func calcBtn(_ sender: UIButton) {
        soyBeanWeight.text = nil
        waterWeight.text = nil
        sugarWeight.text = nil
    }
    
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        switch pickerView.tag {
        case 0:
            return soyBeanUnit.count
        case 1:
            return waterUnit.count
        case 2:
            return sugarUnit.count
        case 3:
            return brixUnit.count
        default:
            return 0
        }
    }
    
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        
        switch pickerView.tag {
        case 0:
            soyBeanUnitSelected = soyBeanUnit[row]
            return soyBeanUnit[row]
        case 1:
            waterUnitSelected = waterUnit[row]
            return waterUnit[row]
        case 2:
            sugarUnitSelected = sugarUnit[row]
            return sugarUnit[row]
        case 3:
            brixUnitSelected = brixUnit[row]
            return brixUnit[row]
        default:
            return "error"
        }
    }
    
    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.
    }


}





Test



Summary

1. 此範例不實作糖與甜度,此部分可留待日後實作
2. 在 textField 中,輸入資料,按下 enter,會啟動 EditingOnExit 事件,可自動帶出換算結果,而 UI 可省去 Calculate 這個 Button


Reference

1. UITextField, https://developer.apple.com/reference/uikit/uitextfield
2. UIViewController, https://developer.apple.com/reference/uikit/uiviewcontroller




/end

2017年5月5日 星期五

範例:簡單型計算機

Objective


此實作參考 [1],而試著將 Objective-C 程式 Port 到 Swift 3.0 程式

Lab


1. 新增 Single View Application 專案
2. UI 設計


Code

1. File Calculation.swift

//
//  Calculation.swift
//  myCaculatorDemo
//
//  Created by Elvis Meng on 2017/5/4.
//  Copyright © 2017 Elvis Meng. All rights reserved.
//

import Foundation

class Calculation : NSObject {

    var operandA : Float?
    var operandB : Float?
    var op : Character?
    var isFirstOperand : Bool
    var result : Float?
    
    override init() {
        operandA = 0.0
        operandB = 0.0
        op = nil
        isFirstOperand = true
        result = 0.0
    }
    
    func calculateResult(operandA : Float, operandB : Float, op : Character) -> Float {
        switch op {
        case "+" :
            result = operandA + operandB
        case "-" :
            result = operandA - operandB
        case "*" :
            result = operandA * operandB
        case "/" :
            result = operandA / operandB
        default :
            print("error")
        }
        return result!
    }
    

}

2. File : ViewController.swift

//
//  ViewController.swift
//  myCaculatorDemo
//
//  Created by Elvis Meng on 2017/4/25.
//  Copyright © 2017 Elvis Meng. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var displayResult: UILabel!
    
    var cal:Calculation = Calculation()
    var isFirstDigit = true
    var hasTapEqual = false
    var operand:Float = 0.0
    var result:Float = 0.0
    var digitCount = 0
    var digit = 0
    
    func processCalc(op:Character){
        if (cal.isFirstOperand){
            cal.operandA = operand
            cal.isFirstOperand = false
        }
        else if (hasTapEqual){
            cal.operandA = result
            cal.operandB = operand
            hasTapEqual = false
        }
        else {
            cal.operandB = operand
            result = cal.calculateResult(operandA: cal.operandA!, operandB: cal.operandB!, op: op)
            displayProcess(num: result)
            cal.operandA = result
        }
        cal.op = op
        operand = 0.0
        digitCount = 0
    }
    
    @IBAction func tapDigit(_ sender: UIButton) {
        digit = sender.tag
        if hasTapEqual == true {
            displayResult.text = "0.0"
            cal.operandA = 0
            cal.operandB = 0
            cal.isFirstOperand = true
            result = 0
            operand = 0
            isFirstDigit = true
            hasTapEqual = false
        }
        if (isFirstDigit && digit == 0){
            isFirstDigit = true
        } else {
            if (digitCount >= 15) {
                return
            }
            isFirstDigit = false
            operand = operand * 10 + Float(digit)
            displayProcess(num: operand)
        }
        digitCount += 1
    }
    
    @IBAction func tapPlus(_ sender: UIButton) {
        processCalc(op: "+")
    }
    
    @IBAction func tapMinus(_ sender: UIButton) {
        processCalc(op: "-")
    }

    @IBAction func tapMultiply(_ sender: UIButton) {
        processCalc(op: "*")
    }
    
    @IBAction func tapDivide(_ sender: UIButton) {
        processCalc(op: "/")
    }
    
    
    @IBAction func tapEqual(_ sender: UIButton) {
        if (cal.isFirstOperand == false){
            cal.operandB = operand
            result = cal.calculateResult(operandA: cal.operandA!, operandB: cal.operandB!, op: cal.op!)
            self.displayProcess(num: result)
            cal.operandA = result
            hasTapEqual = true
        }
    }
    

    @IBAction func tapAC(_ sender: UIButton) {
        displayResult.text = "0.0"
        cal.operandA = 0
        cal.operandB = 0
        cal.isFirstOperand = true
        result = 0
        operand = 0
        isFirstDigit = true
        hasTapEqual = false
    }
    
    func displayProcess(num:Float){
        displayResult.text = String(num)

    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        let cal:Calculation = Calculation()
        cal.isFirstOperand = true
        isFirstDigit = true
        hasTapEqual = false
        displayResult.text = String(0.0)
        
    }

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


}



Test






Reference


1. Chapter 24, 範例程式五:簡易計算器實作, P24-27, “學會Objective-C 的24堂課“,  蔡明志






/end

範例:撥打電話

Objective


學習使用 Button 元件,同時處理多按鈕共用事件

Lab


1. 建立 Single View Application
2. UI 設計

在此畫面需加入 2 張 image,一個作為 Phone Call,另一個為 Phone Hangup


Code

//
//  ViewController.swift
//  MyPhoneDemo
//
//  Created by Elvis Meng on 2017/5/5.
//  Copyright © 2017 Elvis Meng. All rights reserved.
//

import UIKit

var str:String = ""

class ViewController: UIViewController {

    @IBOutlet weak var displayPhoneNumber: UILabel!
    
    @IBAction func tapDigitPad(_ sender: UIButton) {
        str = str + (sender.titleLabel?.text)!
        displayPhoneNumber.text = str
    }
    
    @IBAction func phoneCall(_ sender: UIButton) {
        let url = URL(string: "tel:"+str)
        if #available(iOS 10.0, *) {
            UIApplication.shared.open(url!, options: [:], completionHandler: nil)
        } else {
            UIApplication.shared.openURL(url!)
        }
    }
    
    @IBAction func phoneHangUp(_ sender: UIButton) {
        str = ""
        displayPhoneNumber.text = ""
    }
    
    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.
    }


}


Test




Summary


此範例並處理 Phone Call,也處理 Phone Up


 /end

2017年4月27日 星期四

Error: value of type '[String]' has no member 'removeValueForKey'

Problem

以 removeAtIndex 刪除 Array 中一個 Element,出現下列錯誤:


Solution

Swift 3 不支援 removeAtIndex 刪除 Array 中的一個 Element,而改用:






prettyPrint();