prettyprint

2017年12月22日 星期五

開發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,而改用:






2017年4月20日 星期四

iOS 框架 Framework


iOS 可以分類為 4 個層別 Layer。即:Cocoa Touch,Media 媒體,Core Service 核心服務,以及 Core OS 核心作業系統。[1]

Cocoa Touch 提供使用者控制介面基本物件,例如:按鈕 Button,標籤 Label 等。( UIKit,MapKit,GameKit,MessageUI / AddressBookUI / EventKitUI, Twitter,iAd)

Media 層負責圖像,影音播放,3D 圖像生成等。(AVFoundation ,CoreAudio,CoreImage,CoreGraphics,CoreText,ImageI/O,MediaPlayer,OpenGL ES,QuartzCore)



Core Services 層用來存取較低階的作業系統服務,例如檔案存取,網路,以及許多資料物件類型,我們可透過基礎套件 Foundation 來定義自根類別 NSObject 的所有物件。基礎套件也定義了建立,管理,以及在記憶體中釋放物件的協定 Protocol。(Accounts, AddressBook, CFNetwork, CoreData, CoreFoundation, Foundation, EventKit, CoreLocation, CoreMotion, QuickLook, StoreKit,System Configuration )

Core OS 層包含執行緒,複雜的數學,硬體配件及密碼學等。(Accelerate, ExtterAccessory,Security,System)

框架 Framework 是個工具箱 Tool Box,此工具箱由各種類別程式庫 Class Library 組成,作為開發 App 的基本套件。

 Advanced Study 

1. Cocoa 基礎指南:
2.  iOS 人機界面指南


 Reference 


1. https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/OSX_Technology_Overview/CoreServicesLayer/CoreServicesLayer.html

2. Cocoa Layered Architecture for Mac OS, http://www.knowstack.com/cocoa-layered-architecture-for-mac-osx/

/end
prettyPrint();