鍵盤(pán)屬性
鍵盤(pán)的外觀由一系列叫做 UITextInputTraits 的 UITextField 屬性決定门驾。其中一個(gè)屬性就是展示的鍵盤(pán)的類(lèi)型收恢。這個(gè)程序中, 你需要使用數(shù)字鍵盤(pán)。
選中文本框, 在屬性指示器面板里面找到 Keyboard Type, 選擇 Number Pad, 并把 Correction 和 Spell Checking 修改為 NO。
響應(yīng)文本框的更改
下一步是當(dāng)文本被鍵入到文本框中時(shí), 更新 Celsius Label。你需要寫(xiě)點(diǎn)代碼來(lái)完成這個(gè)任務(wù)。
目前, 它會(huì)響應(yīng)在 ViewController.swift 中定義的 ViewController 類(lèi)艺智。然而, 對(duì)于管理華氏溫度和攝氏溫度轉(zhuǎn)換的視圖控制器來(lái)說(shuō), ViewController 不是一個(gè)很好的描述性的名字。擁有一個(gè)描述性的類(lèi)型名允許你在工程變得更大的時(shí)候更容易地管理它秉溉。你將刪除這個(gè)文件并使用一個(gè)更具描述性的類(lèi)來(lái)代替它力惯。
刪除 ViewController.swift 并新建一個(gè) ConversionViewController 類(lèi)。在 ConversionViewController.swift 中:
import UIKit
class ConversionViewController: UIViewController {
}
即聲明一個(gè)名為 ConversionViewController 的類(lèi)繼承自 UIViewController≌偎唬現(xiàn)在你需要在 Main.storyboard 中把你創(chuàng)建的界面和這個(gè)你定義的新的視圖控制器關(guān)聯(lián)在一塊兒父晶。
打開(kāi) Main.storyboard 并選擇 ConversionViewController 這個(gè)視圖控制器, 要么在左側(cè)的文檔大綱中, 要么點(diǎn)擊視圖控制器上面的黃色圓圈。
打開(kāi)身份檢查器, 即工具視圖中的第三個(gè) tab(Command-Option-3)弄跌。 在最上面, 找到 Custom Class 一欄, 并把 Class 修改為 ConversionViewController甲喝。
文本框是另外一種控制(UIButton 和 UITextField 都是 UIControl 的子類(lèi))并且都能在文本發(fā)生改變時(shí)發(fā)送事件。
為了完成這個(gè), 你需要為 Celsius 文本框創(chuàng)建出口(outlet)并在文本發(fā)生改變時(shí)為調(diào)用的文本框創(chuàng)建動(dòng)作(action)铛只。
打開(kāi) ConversionViewController.swift 并定義這個(gè) outlet 和 action埠胖。 現(xiàn)在, label 會(huì)被更新為用戶(hù)輸入到文本框中的任何東西糠溜。
class ConversionViewController: UIViewController {
@IBOutlet var celsiusLabel: UILabel!
@IBAction func fahreheitFieldEditingChanged(textField: UITextField) {
celsiusLabel.text = textField.text
}
}
打開(kāi) Main.storyboard 來(lái)完成這些連接。點(diǎn)擊屏幕右上角的兩個(gè)相交圓, 按住 Ctrl 鍵從 Conversion View Controller 中把 100 拖到右側(cè) ConversionViewController.swift 中的 celsiusLabel 代碼中, 或者從 celsiusLabel 變量前面的小空心圓中拖到 ConversionViewController 中的 100 這個(gè)視圖上去直撤。
連接 action 會(huì)有一點(diǎn)不同, 因?yàn)槟阆胍诰庉嫲l(fā)生變化時(shí)觸發(fā)這個(gè)動(dòng)作(action)非竿。
在畫(huà)板中選中文本框并從工具面板中打開(kāi)它的連接檢查器(最右邊一個(gè) tab, 或者 Command-Option-6)。 連接檢查器是一個(gè)用來(lái)設(shè)置連接并查看已經(jīng)設(shè)置了什么連接的絕佳場(chǎng)所谋竖。
你想讓發(fā)生在文本框中的變化觸發(fā)你定義在 ConversionViewController 中的動(dòng)作(action)红柱。在連接檢查器中, 定位到 Sent Events 和 Editing Changed 事件。點(diǎn)擊并拖拽右側(cè)的 Editing Changed 圓圈到 Conversion View Controller 并在彈出的菜單中點(diǎn)擊 fahrenheitFieldEditingChanged: action蓖乘。
構(gòu)造并運(yùn)行程序锤悄。點(diǎn)擊文本框并輸入一些數(shù)字。Celsius label 會(huì)輸出和文本框中輸入的一樣文本嘉抒。如果把文本框里的內(nèi)容全部刪除了, 注意你會(huì)看到 100 那個(gè)位置的 label 好像不見(jiàn)了零聚。不含文本的 label 擁有寬和高都為 0 的固有內(nèi)容, 所以它下面的 labels 會(huì)向上移動(dòng)。我們來(lái)修復(fù)這個(gè)問(wèn)題些侍。
在 ConversionViewController.swift 中, 更新 fahrenheitFieldEditingChanged(_:), 當(dāng)文本為空的時(shí)候讓 label 展示 "???"隶症。
@IBAction func fahrenheitFieldEditingChanged(textField: UITextField) {
if let text = textField.text where !text.isEmpty {
celsiusLabel.text = text
}
else {
celsiusLabel.text = "???"
}
}
銷(xiāo)毀鍵盤(pán)(dismissing the keyboard)
一種通常的做法是檢測(cè)當(dāng)用戶(hù)敲擊 Return 鍵時(shí)用那個(gè) action 來(lái)注銷(xiāo)鍵盤(pán)。你將在第 13 章中使用這個(gè)方法娩梨。因?yàn)閿?shù)字鍵盤(pán)沒(méi)有 Return 鍵, 你得允許用戶(hù)敲擊背景視圖來(lái)觸發(fā)注銷(xiāo)鍵盤(pán)的動(dòng)作沿腰。
當(dāng)輕敲文本框時(shí),會(huì)在文本框身上調(diào)用 becomeFirstResponder() 览徒。這會(huì)引起鍵盤(pán)出現(xiàn)狈定。為了注銷(xiāo)鍵盤(pán), 你需要在文本框身上調(diào)用 resignFirstResponder() 。你會(huì)在第 13 章中學(xué)到更多關(guān)于這些方法的東西习蓬。
為了完成這個(gè), 你需要為文本框設(shè)置一個(gè)出口(outlet), 和一個(gè)被觸發(fā)的方法當(dāng)背景視圖被輕點(diǎn)時(shí)纽什。這個(gè)方法會(huì)在文本框 outlet 身上調(diào)用 resignFirstResponder() 方法。
打開(kāi) ConversionViewController.swift 并在靠近最上面的位置申明一個(gè) outlet 以引用文本框躲叼。
@IBOutlet var celsiusLabel: UILabel!
@IBOutlet var textField: UITextField!
現(xiàn)在實(shí)現(xiàn) action 方法以在被調(diào)用時(shí)注銷(xiāo)鍵盤(pán)芦缰。
@IBAction func dismissKeyboard(sender: AnyObject) {
textField.resignFirstResponder()
}
仍然還需要 2 個(gè)東西, textField outlet 需要在 storyboard 文件中被連接, 還有你需要一種方式來(lái)觸發(fā)你添加的 dismissKeyboard(_:) 方法。
首先考慮第一項(xiàng), 打開(kāi) Main.storyboard 并選擇 Conversion View Controller枫慷。 從 Conversion View Controller 中拖拽到畫(huà)布中的文本框里并把它連接到 textField outlet让蕾。
現(xiàn)在你需要一種方法來(lái)觸發(fā)你實(shí)現(xiàn)的 action 方法。你將使用手勢(shì)識(shí)別來(lái)完成它或听。
手勢(shì)識(shí)別是 UIGestureRecognizer 的一個(gè)子類(lèi), 它檢測(cè)一系列特定的觸摸事件并在序列被檢測(cè)到時(shí)在它的目標(biāo)上(target)調(diào)用一個(gè)動(dòng)作(action)探孝。手勢(shì)識(shí)別有輕敲、滑動(dòng)誉裆、長(zhǎng)按等等顿颅。在這一章, 你會(huì)學(xué)習(xí)使用 UITapGestureRecognizer 來(lái)檢測(cè)用戶(hù)輕敲背景視圖。你會(huì)在第 18 章學(xué)到更多關(guān)于手勢(shì)識(shí)別的東西足丢。
在 Main.storyboard, 在對(duì)象庫(kù)中找到 Tap Gesture Recognizer 粱腻。把這個(gè)對(duì)象拖拽到 Conversion View Controller 的背景視圖上庇配。你會(huì)在 scene dock 里看見(jiàn)該手勢(shì)識(shí)別的引用。
在 scene dock 中按住 Ctrl 鍵把該手勢(shì)識(shí)別拖拽到 Conversion View Controller 中并把它連接到 dismissKeyboard 方法上绍些。
實(shí)現(xiàn)溫度轉(zhuǎn)換
界面基本完成了, 讓我們來(lái)實(shí)現(xiàn)華氏溫標(biāo)到攝氏溫標(biāo)的轉(zhuǎn)換捞慌。你將存儲(chǔ)當(dāng)前華氏溫標(biāo)的值并計(jì)算攝氏溫度的值當(dāng)文本框的值改變時(shí)。
在 ConversionViewController.swift 中, 為 Fahrenheit 值添加一個(gè)屬性柬批。這會(huì)是一個(gè)可選類(lèi)型的 double 值(一個(gè) Double?)卿闹。
@IBOutlet var celsiusLabel: UILabel!
var fahrenheitValue: Double?
這個(gè)屬性為什么是可選的是因?yàn)橛脩?hù)可能沒(méi)有鍵入一個(gè)數(shù)字, 和之前你修復(fù)的空字符串問(wèn)題類(lèi)似。
現(xiàn)在為 Celsius 值添加一個(gè)計(jì)算屬性萝快。這個(gè)值將會(huì)基于 Fahrenheit 值被計(jì)算出來(lái)锻霎。
var fahrenheitValue: Double?
var celsiusValue: Double? {
if let value = fahrenheitValue {
return (value - 32) * (5/9)
}
else {
return nil
}
}
數(shù)字格式化程序
按照以上的步奏構(gòu)建并運(yùn)行程序后, 會(huì)出現(xiàn)小數(shù)點(diǎn)的問(wèn)題。現(xiàn)在我們修復(fù)它揪漩。你使用一個(gè) number formatter 來(lái)自定義數(shù)字的顯示旋恼。
在 ConversionViewController.swift 中創(chuàng)建一個(gè)常量數(shù)字格式化程序。
let numberFormatter: NSNumberFormatter = {
let nf = NSNumberFormatter()
nf.numberStyle = .DecimalStyle
nf.minimumFractionDigits = 0
nf.maxmumFractionDigits = 1
return nf
}()
這兒你使用了閉包來(lái)實(shí)例化一個(gè)數(shù)字格式化程序奄容。你在創(chuàng)建一個(gè) .Decimal 風(fēng)格的 NSNumberFormatter, 小數(shù)點(diǎn)不多于 1 位冰更。
現(xiàn)在更新下 updateCelsiusLabel() 以使用該格式化程序。
func updateCelsiusLabel() {
if let value = celsiusValue {
celsiusLabel.text = numberFormatter.stringFromNumber(value)
}
else {
celsiusLabel.text = "???"
}
}
代理(Delegation)
代理是回調(diào)的一種面向?對(duì)象的方法昂勒∈裣福回調(diào)(callback)是一個(gè)在事件之前提供的函數(shù)并且每次事件發(fā)生時(shí)都會(huì)調(diào)用該函數(shù)。有些對(duì)象需要執(zhí)行多個(gè)事件的回調(diào)戈盈。例如, 當(dāng)用戶(hù)鍵入文本時(shí)還有當(dāng)用戶(hù)按下 Return 鍵時(shí)文本框?qū)?zhí)行"回調(diào)"奠衔。
然而, 沒(méi)有內(nèi)置的方式用于兩個(gè)(或更多)回調(diào)函數(shù)之間協(xié)同和分享信息。這是代理所強(qiáng)調(diào)的問(wèn)題 — 你提供單個(gè)代理來(lái)接收特定對(duì)象的所有跟事件相關(guān)的回調(diào)塘娶。這個(gè)代理對(duì)象就能存儲(chǔ)归斤、操作、作用于和依賴(lài)從回調(diào)發(fā)出的信息刁岸。
當(dāng)用戶(hù)往文本框中鍵入東西時(shí), 那個(gè)文本框就會(huì)詢(xún)問(wèn)它的代理是否想要接受用戶(hù)做出的更改脏里。對(duì)于 WorldTrotter, 如果用戶(hù)嘗試輸入第二個(gè)數(shù)字分隔符, 就拒絕更改。文本框的代理將會(huì)是 ConversionViewController 的一個(gè)實(shí)例虹曙。
遵守協(xié)議
第一步就是使 ConversionViewController 類(lèi)的實(shí)例執(zhí)行 UITextField 代理的角色, 通過(guò)聲明 ConversionViewController 遵守 UITextFieldDelegate 協(xié)議迫横。對(duì)于每個(gè)代理角色, 都有一個(gè)對(duì)應(yīng)的協(xié)議, 它里面聲明了能在它的代理身上對(duì)象能調(diào)用的方法。
UITextFieldDelegate 協(xié)議看起來(lái)像這樣:
protocol UITextFieldDelegate: NSObjecProtocol {
optional func textFieldShouldBeginEditing(textField: UITextField) -> Bool
optional func textFieldDidBeginEditing(textField: UITextField)
optional func textFieldShouldEndEditing(textField: UITextField) -> Bool
optional func textFieldDidEndEditing(textField: UITextField)
optional func textField(textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String) -> Bool
optional func textFieldShouldClear(textField: UITextField) -> Bool
optional func textFieldShouldReturn(textField: UITextField) -> Bool
}
這個(gè)協(xié)議, 像所有協(xié)議一樣, 是由 protocol 關(guān)鍵字后面跟著協(xié)議名UITextFieldDelegate來(lái)聲明的酝碳。冒號(hào)后面的 NSObjectProtocol 引用 NSObject 協(xié)議并告訴你 UITextFieldDelegate 繼承了 NSObject 協(xié)議中的所有方法矾踱。UITextFieldDelegate 特有的方法在下面定義。
你不能創(chuàng)建 protocol 實(shí)例; 它僅僅是一個(gè)方法和屬性的列表击敌。代替的是, 實(shí)現(xiàn)留給了遵守該協(xié)議的每個(gè)類(lèi)型介返。
要讓類(lèi)遵守一個(gè)協(xié)議, 需要把協(xié)議名放在冒號(hào)后面, 如果該類(lèi)有父類(lèi), 則需要用逗號(hào)分割父類(lèi)和協(xié)議名, 把協(xié)議名放在父類(lèi)的后面。在 ConversionViewController.swift 中, 聲明那個(gè)ConversionViewController 遵守 UItextFieldDelegate 協(xié)議。
class ConversionViewController: UIViewController, UITextFieldDelegate {
}
用在代理中的協(xié)議叫做代理協(xié)議, 代理協(xié)議的命名約定是代理類(lèi)的名字加上單詞 Delegate圣蝎。不是所有的協(xié)議都是代理協(xié)議, 然而你會(huì)在第 15 章看到不同種類(lèi)協(xié)議的一個(gè)例子刃宵。目前我們提到的協(xié)議是 iOS SDK 的一部分, 但是你也可以寫(xiě)自己的協(xié)議。
使用代理
現(xiàn)在你已經(jīng)聲明了 ConversionViewController 遵守 UITextFieldDelegate 協(xié)議的類(lèi), 你可以設(shè)置文本框的 delegate 屬性了徘公。
打開(kāi) Main.storyboard 并按住 Ctrl 鍵拖拽文本框到 Conversion View Controller 中(拖到最上面的小黃圈中)牲证。在彈出的菜單中選擇 delegate。
下一步, 你將實(shí)現(xiàn)你感興趣的 UITextFieldDelegate 方法 — textField(_:shouldChangeCharactersInRange:replacementString:)关面。 因?yàn)槲谋驹谒拇砩险{(diào)用這個(gè)方法, 你必須在 ConversionViewController.swift 中實(shí)現(xiàn)這個(gè)方法坦袍。
在 在 ConversionViewController.swift 中實(shí)現(xiàn) textField(_:shouldChangeCharactersInRange:replacementString:) 以打印文本框當(dāng)前的文本還有替換字符串。現(xiàn)在, 只從該方法中返回 true 等太。
func textField(textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String) -> Bool {
print("current text: \(textField.text)")
print("Replacement text: \(string)")
return true
}
從邏輯上講, 如果已經(jīng)存在的字符串擁有一個(gè)小數(shù)分隔符并且替換字符串也擁有一個(gè)小數(shù)分隔符, 那么更改會(huì)被拒絕捂齐。
更新 textField(_:shouldChangeCharactersInRange:replacementString:) 來(lái)使用該邏輯。
func textField(textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String) -> Bool {
let existingTextHasDecimalSeparator = textField.text?.rangeOfString('.')
let replacementTextHasDecimalSeparator = string.rangeOfString(".")
if existingTextHasDecimalSeparator != nil &&
replacementTextHasDecimalSeparator != nil {
return false
}
else {
return true
}
}
運(yùn)行這個(gè)程序, 當(dāng)你輸入多個(gè)小數(shù)點(diǎn)時(shí), 程序會(huì)拒絕讓你輸入多余1個(gè)的小數(shù)點(diǎn)缩抡。
挑戰(zhàn)
禁止用戶(hù)輸入字母字符奠宜。