iOS Programming-第四章 文本輸入和代理

鍵盤(pán)屬性


鍵盤(pán)的外觀由一系列叫做 UITextInputTraitsUITextField 屬性決定门驾。其中一個(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甲喝。

文本框是另外一種控制(UIButtonUITextField 都是 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 EventsEditing Changed 事件。點(diǎn)擊并拖拽右側(cè)的 Editing Changed 圓圈到 Conversion View Controller 并在彈出的菜單中點(diǎn)擊 fahrenheitFieldEditingChanged: action蓖乘。

img

構(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 方法上绍些。

img

實(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ù)輸入字母字符奠宜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瞻想,隨后出現(xiàn)的幾起案子压真,更是在濱河造成了極大的恐慌,老刑警劉巖蘑险,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滴肿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡佃迄,警方通過(guò)查閱死者的電腦和手機(jī)泼差,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)和屎,“玉大人拴驮,你說(shuō)我怎么就攤上這事〔裥牛” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵宽气,是天一觀的道長(zhǎng)随常。 經(jīng)常有香客問(wèn)我,道長(zhǎng)萄涯,這世上最難降的妖魔是什么绪氛? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮涝影,結(jié)果婚禮上枣察,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好序目,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布臂痕。 她就那樣靜靜地躺著,像睡著了一般猿涨。 火紅的嫁衣襯著肌膚如雪握童。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,950評(píng)論 1 291
  • 那天叛赚,我揣著相機(jī)與錄音澡绩,去河邊找鬼。 笑死俺附,一個(gè)胖子當(dāng)著我的面吹牛肥卡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播事镣,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼召调,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蛮浑?” 一聲冷哼從身側(cè)響起唠叛,我...
    開(kāi)封第一講書(shū)人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沮稚,沒(méi)想到半個(gè)月后艺沼,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蕴掏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年障般,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盛杰。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挽荡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出即供,到底是詐尸還是另有隱情定拟,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布逗嫡,位于F島的核電站青自,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏驱证。R本人自食惡果不足惜延窜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抹锄。 院中可真熱鬧逆瑞,春花似錦荠藤、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至谋减,卻和暖如春牡彻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背出爹。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工庄吼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人严就。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓总寻,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親梢为。 傳聞我的和親對(duì)象是個(gè)殘疾皇子渐行,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容