前言

正如一个朋友说得:“开发这个工作讲究的就是自学能力。” 我最近开发一款 App 希望能够同时阅读 Twitter 和微博,接触到官方文档时候,就想偷懒去开源第三方 SDK,发现太多bug。还不如官方文档解释的清楚和步骤详细。
而且忘记了方法论,就是开发一款 App,罗列出 UI 和技术框架,但是遇到全英文的文档时候也懵了。仔细想想其实也就是 TwitterKit 封装 iOS 一些常见用法的 API,但是总的来说还是基于 oAuth 和 REST ,其中 REST 都很熟悉了,无非就是 oAuth 比较麻烦一般都是每个公司封装不一样,需要安装文档一步一步来配置就好了。

总结下来,遇到新的技术要注意方法:

  • 对比是否接触过类似的技术问题
  • 总结提取大纲分类
  • 不认识单词一定要翻译清楚
  • 实践部分一步一步的做

吐槽一下 dev.twitter.com 的三级菜单真是很隐晦,不是一个好的设计。
dev.twitter.com

Twitter 集成 TwitterKit

下载 Fabric.app,添加 pod,安装提示步骤来即可。

1
2
pod 'Fabric'
pod 'TwitterKit'

访问:https://docs.fabric.io/ios,使用其 Authentication 和 REST API 即可。

The REST API can be used to make authenticated Twitter API requests. Though it can be accessed manually, we recommend using the convenience methods whenever possible.

微博集成 LeanCloudSocial

1
2

pod 'LeanCloudSocial'

文档:https://leancloud.cn/docs/sns.html

LeanCloudSocial是只有一键登录,属于轻量级添加微博/微信/QQ登录支持,没有其他微博相关 REST API 可用。后面微博部分需要手动或者可能换成微博官方 SDK。

The Swift Programming Language 2.1 Examples

源码在 GitHub:https://github.com/gewill/The-Swift-Programming-Language-2.1-Examples

Playground ->

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//: Playground - noun: a place where people can play

import UIKit

//: 方法(Methods)
//: 方法是与某些特定类型相关联的函数。类、结构体、枚举都可以定义实例方法;实例方法为给定类型的实例封装了具体的任务与功能。类、结构体、枚举也可以定义类型方法;类型方法与类型本身相关联。类型方法与 Objective-C 中的类方法(class methods)相似。

//: 实例方法 (Instance Methods)
//: 实例方法是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与实例目的相关的功能,并以此来支撑实例的功能。实例方法的语法与函数完全一致.

//: self 属性(The self Property)
//: 类型的每一个实例都有一个隐含属性叫做self,self完全等同于该实例本身。


//: 在实例方法中修改值类型(Modifying Value Types from Within Instance Methods)
//: 结构体和枚举是值类型。一般情况下,值类型的属性不能在它的实例方法中被修改。但是,如果你确实需要在某个特定的方法中修改结构体或者枚举的属性,你可以选择变异(mutating)这个方法,然后方法就可以从方法内部改变它的属性;并且它做的任何改变在方法结束时还会保留在原始结构中。方法还可以给它隐含的self属性赋值一个全新的实例,这个新实例在方法结束后将替换原来的实例。
struct Point {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}

var somePoint = Point(x: 3.0, y: 3.0)
somePoint.moveByX(2.0, y: 3.0)


//: 在可变方法中给 self 赋值(Assigning to self Within a Mutating Method)
struct Point1 {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
self = Point1(x: x + deltaX, y: y + deltaY)
}
}
//: 这里不仅仅学习一个语法,而是思考为什么费劲把默认不可变的枚举变成可变,是因为提供一个可能结合枚举特性,写出简洁表达能力强的代码。
//: Ash Furrow: Ideas vs Syntax. Watch video here: https://youtu.be/YsUTuwpbURA
enum TriStateSwitch {
case Off, Low, High
mutating func next() {
switch self {
case Off: self = Low
case Low: self = High
case High: self = Off
}
}
}

var ovenLight = TriStateSwitch.Low
ovenLight.next()
ovenLight.next()
//: 类型方法 (Type Methods)

//: 实例方法是被类型的某个实例调用的方法。你也可以定义类型本身调用的方法,这种方法就叫做类型方法。声明结构体和枚举的类型方法,在方法的func关键字之前加上关键字static。类可能会用关键字class来允许子类重写父类的方法实现。


//: 在 Objective-C 中,你只能为 Objective-C 的类定义类型方法(type-level methods)。在 Swift 中,你可以为所有的类、结构体和枚举定义类型方法。每一个类型方法都被它所支持的类型显式包含。

class SomeClass {
static func someTypeMethod() {
// Do something
print("This is a type method")
}
}
SomeClass.someTypeMethod()


//: 下面的例子定义了一个名为LevelTracker结构体。它监测玩家的游戏发展情况(游戏的不同层次或阶段)
//: 也就是定结构体类型方法的用途,可以直接把逻辑加到定义的数据类型中

struct LevelTracker {
static var highestunlockedLevel = 1
static func unlockLevel(level: Int) {
if level > highestunlockedLevel { highestunlockedLevel = level }
}
static func levelIsUnlocked(level: Int) -> Bool {
return level <= highestunlockedLevel
}
var currentLevel = 1
mutating func advanceToLevel(level: Int) -> Bool {
if LevelTracker.levelIsUnlocked(level) {
currentLevel = level
return true
} else {
return false
}
}
}

class Player {
var tracker = LevelTracker()
let playName: String
func compeletedLevel(level: Int) {
LevelTracker.unlockLevel(level + 1)
tracker.advanceToLevel(level + 1)
}
init(name:String) {
playName = name
}
}


var player = Player.init(name: "Will")
player.compeletedLevel(4)
print(LevelTracker.highestunlockedLevel)

player = Player.init(name: "Ge")
if player.tracker.advanceToLevel(6) {
print("Level 6 has been unlocked.")
} else {
print("Level 6 has not yet been unlocked.")
}

The Swift Programming Language 2.1 Examples

源码在 GitHub:https://github.com/gewill/The-Swift-Programming-Language-2.1-Examples

Playground ->

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//: Playground - noun: a place where people can play

import UIKit

//: 属性将值和特定的类、结构或枚举关联。存储属性存储常量或变量作为实例的一部分,而计算属性计算一个值。计算属性可以用于类、结构体和枚举,存储属性只能用于类和结构体。


//: 存储属性(Stored Properties)
struct FiexdLengthRange {
var firstValue: Int
let length: Int
}

var x = FiexdLengthRange(firstValue: 1, length: 3)
x.firstValue = 4

//: 常量结构体的存储属性均为常量
let y = FiexdLengthRange(firstValue: 4, length: 2)


//: 延迟存储属性
class DataImporter {
var firstName = "data.txt"
}

class DataManager {
lazy var importer = DataImporter()
var data = [String]()
}

let manager = DataManager()
manager.data.append("New")
manager.data.append("Two")


//: 存储属性没有实例变量,属性的全部信息——包括命名、类型和内存管理特征——都在唯一一个地方(类型定义中)定义。


//: 计算属性(Computed Properties): 除存储属性外,类、结构体和枚举可以定义计算属性。计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。
struct Point {
var x = 0.0, y = 0.0
}

struct Size {
var width = 0.0, height = 0.0
}

struct Rect {
var origin = Point()
var size = Size()
var center: Point {
mutating get {
let centerX = origin.x + size.width / 2
let centerY = origin.y + size.height / 2
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - size.width / 2
origin.y = newCenter.y - size.height / 2
}
}
}


var rect0 = Rect(origin: Point(), size: Size(width: 3.0, height: 4.0))
rect0.center
rect0.center = Point(x: 50, y: 50)
rect0.center

//: 只读计算属性(Read-Only Computed Properties)
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
Cuboid(width: 4.0, height: 5.0, depth: 2.0).volume


//: 属性观察器(Property Observers)
//: 属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,甚至新值和当前值相同的时候也不例外。不需要为非重写的计算属性添加属性观察器,因为可以通过它的 setter 直接监控和响应值的变化。

class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotolSteps) {
if totalSteps != newTotolSteps {
print("The totalSteps will change value to \(totalSteps).")
}
}
didSet {
if totalSteps > oldValue {
print("The totalSteps add \(totalSteps - oldValue) steps. ")
}
}
}
}

StepCounter().totalSteps = -33
var stepCounter = StepCounter()
stepCounter.totalSteps = 33
stepCounter.totalSteps = 11



//: 全局变量和局部变量(Global and Local Variables)
//: 全局的常量或变量都是延迟计算的,跟延迟存储属性相似,不同的地方在于,全局的常量或变量不需要标记lazy修饰符。局部范围的常量或变量从不延迟计算。


//: 类型属性(Type Properties)
//: 使用关键字static/class来定义类型属性,必须指定默认值,延迟初始化的。类型属性是通过类型本身来访问

struct SomeStructure {
static var storedTypedProperty = "some value"
static var computedTypedProperty: Int {
return 1
}
}

enum SomeEnumeration {
static var storedTypedProperty = "some value"
static var computedTypedProperty: Int {
return 6
}
}
class SomeClass {
static var storedTypedProperty = "some value"
static var computedTypedProperty: Int {
return 7
}

class var overrideableComputedTypeProperty: Int {
return 109
}
}


struct AudioChannel {
static let threshoudLevel = 10
static var maxInputLevelForAllChannels = 0
var currentLevel: Int = 0 {
didSet {
if currentLevel > AudioChannel.threshoudLevel {
currentLevel = AudioChannel.threshoudLevel
}
if currentLevel > AudioChannel.maxInputLevelForAllChannels {
AudioChannel.maxInputLevelForAllChannels = currentLevel
}
}
}
}

var leftChannel = AudioChannel()
var rightChannel = AudioChannel()

leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
print(AudioChannel.maxInputLevelForAllChannels)

rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
print(AudioChannel.maxInputLevelForAllChannels)


Watch video here: https://youtu.be/YsUTuwpbURA

It’s much clear to understand what’s the Ash talk about by list the outlines

1. Agenda

  • We’ve been here before
  • Learning is forever, deal with it
  • Never throw ideas away
  • How to force yourself to think
  • Always be abstracting

2. Ideas vs Syntax

Object Literals/Blocks & GCD/Swift 2: Guard/Currying/Enums

New syntax lets us do new things
However! Syntax is only a tool
Like blocks, Swift 2 syntax is most useful when it enables new ideas

That’s all just syntax. What matters are ideas.

3. Benefits of Testing

  • Limited object scope is good
    • High cohesion, low coupling
  • How to limit scope?
    • Controlling public interface and dependencies

Things to never throw away:Code & Ideas

Changing a unit test?

  • No -> Refactoring
  • Yes -> Rewriting

4. Dependency Injection

Your things shouldn’t create the things they need

1
class ViewController: UIViewController {
let networkController = NetworkController()
    func viewDidLoad() {
        super.viewDidLoad()
        networkController.fetchStuff {
            self.showStuff()
        }
} }
1
class ViewController: UIViewController {
var networkController: NetworkController?
    func viewDidLoad() {
        super.viewDidLoad()
        networkController?.fetchStuff {
            self.showStuff()
        }
} }

5. Unit Testing

  • Don’t test private functions
    • Also, start marking functions as private
  • Remember, we want to avoid rewriting
  • Don’t test the implementation
  • Don’t use “partial mocks”
    • See @searls post on partial mocks

6. Wrap Up

  • We have a history of being awesome, let’s keep it up
  • Learning isn’t just for when Xcode is in beta
  • Ideas are more valuable than code, but throwing away either is dangerous
  • Effective unit tests make it easy to change code
  • Operate at the highest level of abstraction you can at any given time

1. 前言

以前只知道怎么用 Delegate,能够传值和回传值。最近写代码封装一个 UIPickerView+UIButton,时候需要传值,就想到了 Delegate。但是自己写一个却把我难住了。最后看了还是看了一个 Demo, 单步调试才算真正理解的Delegate 整个流程和原理。

简单来说就是声明 Protocol,添加触发 调用Protocol 方法的条件,被委托者设置 Delegate = self,就会在触发条件下调用 Protocol 方法(参数和返回值可实现双向传值)。

主要看了这个教程:
CREATE CUSTOM DELEGATE AND PROTOCOL IOS | SWIFT & OBJECTIVE-C,下面是作者提供的源码Objective-C  ProjectSwift Project

教材中MyTimer是Custom ViewController,我实际工作中是 UIView,也是参考了另外一篇 Apple 的教程:Implement a Custom Control。发现开发真的离不开 Google,但是前提是要有思路,或者对一个项目或者难题,要有整体的框架和可用的知识点熟悉。

2. 源码

下面是UIPickerView+UIButton封装后的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//  GenderPickerView.swift

import UIKit

protocol GenderPickerViewDelegate: NSObjectProtocol {
func genderPickerDidSelectItem(row: Int)
}

class GenderPickerView: UIView, UIPickerViewDataSource, UIPickerViewDelegate {
var delegate: GenderPickerViewDelegate!
var toolbar: UIToolbar!
var picker: UIPickerView!
var genderPickerViewData = ["不告诉你", "男", "女"]
var genderSelectedRow = 0

override init(frame: CGRect) {
super.init(frame: frame)
toolbar = UIToolbar.init(frame: CGRectMake(0, 0, UIScreen.mainScreen().bounds.width, 20))
toolbar.barStyle = .Default
toolbar.sizeToFit()

let cancelButton = UIBarButtonItem.init(title: "取消", style: UIBarButtonItemStyle.Done, target: self, action: "genderPickerDidCancel")
let flexibleButton = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: self, action: nil)
let doneButton = UIBarButtonItem.init(title: "确定", style: UIBarButtonItemStyle.Done, target: self, action: "genderPickerDidSelect")
toolbar.setItems([cancelButton, flexibleButton, doneButton], animated: true)

picker = UIPickerView.init(frame: CGRectMake(0, 44, UIScreen.mainScreen().bounds.width, 120))
picker.dataSource = self
picker.delegate = self

addSubview(toolbar)
addSubview(picker)
}

convenience init() {
self.init(frame: CGRectMake(0, UIScreen.mainScreen().bounds.height - 164, UIScreen.mainScreen().bounds.width, 164))
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}


//MARK: - 性别选择视图
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}

func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return 3
}

func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return genderPickerViewData[row]
}

func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
genderSelectedRow = row
}

func genderPickerDidSelect() {
delegate?.genderPickerDidSelectItem(genderSelectedRow)
removeFromSuperview()
}

func genderPickerDidCancel() {
removeFromSuperview()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//  PersonalInfoViewController.swift

import UIKit

class ViewController: UIViewController, GenderPickerViewDelegate {
var genderPicker: GenderPickerView!
override func viewDidLoad() {
super.viewDidLoad()
genderPicker = GenderPickerView.init()
genderPicker.delegate = self
self.view.addSubview(genderPicker)}

//MARK: - GenderPickerViewDelegate
func genderPickerDidSelectItem(row: Int) {
// Do something
}
}

Git开始使用的是 GitHub 的 GUI,基本上就是Commit和Sync,涉及项目协作是 Pull Request,基本没问题,但是整个Git的流程和原理还是不懂。

最近公司项目是命令行,涉及合并冲突工作流等等,还是命令行来的清晰直接,又仔细看了Pro Git Book,这里梳理一下Git的工作原理和常用的命令。

给他的工作流程如下:

  • 多个分支:checkout branch
  • 上传修改:add/commit/push
  • 避免冲突:Xcode 每次只提交个人修改的文件,之后可以舍弃多余修改,尤其是 xib 文件,只要打开就会被修改
  • 合并分支:merge

其中2个概念要理清楚,一个是分支用来分版本分人员的作用,另一个是本地工作区和远程仓库。工作原理和概念理解了,常用的几个命令实践几次也就记住了,太多不常用甚至没用命令,需要时可在 Google。

The Swift Programming Language 2.1 Examples

源码在 GitHub:https://github.com/gewill/The-Swift-Programming-Language-2.1-Examples

Playground ->

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//: Playground - noun: a place where people can play

import UIKit

//: 类和结构体对比
struct Resolution {
var width = 0
var height = 0
}

class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}

//: 类和结构体实例
//: 一个引用类型一个值类型 Classes are reference types, Structures and Enumerations are value types

let someResolution = Resolution()
let someVideoMode = VideoMode()

someResolution.height
someVideoMode.frameRate
someVideoMode.resolution.height

let vga = Resolution(width: 640, height: 480)
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.height = 3096
cinema.width
hd
let tenEighty = VideoMode()
var asloTenEighty = tenEighty
asloTenEighty.frameRate = 30.0
tenEighty.frameRate


//: 恒等运算符 Identity Operators : === !==
if tenEighty === asloTenEighty {
print("These two variables refer to the same instance")
}

//: 指针 Pointers
//: Swift指针类型不用带 *

//: 类和结构体的选择: 结构体适合存储简单数据值和拷贝行为, 不需要继承另一个类型属性或者行为

//: String/Array/Dictionary 类型的赋值和复制行为:Assignment and copy behaviors on Strings, Arrays and Dicitonaries

//: Swift 均以结构体形式实现,值拷贝,但是处于性能优化,只哟确有必要时才会实际执行拷贝;Objective-C是类,传递的是实例的引用。


最近学会了使用 Surge + Shadowsocks,终于可以优雅地在 iOS上分流模式上网了。

购买的是 host1plus.com的VPS,$2一个月好便宜的说。只有一个SSH ,一切都是命令行搞定。折腾了几天后发现,命令行完全是一个简洁高效操作计算机的方式。相比GUI的程序,唯一的缺点:可操作项和文件目录等不能够一目了然的看到,最多一个--help,还是不行就要求助Google了。当然熟悉了命令行之后这也不是问题,就是记忆多一些嘛。

首先安装了Shadowsocks,后来有安装了 WordPress 256的内存瞬间就满了。使用speedtest-cli测试下来, 主机的速度1000M/100M,但是连接上海服务器就只有20M/2M左右。手机也算够用了,毕竟电脑上有强大的Lantern。

最坑的是一般搜索的官方教程都是有坑的,一般都要找一些个人安装文章才能完美运行。PHP和FTP都是找了好多教程才搞定的,下次在配置一定官方+个人教程,搜索的时候也注意筛选最近一周或一月的结果比较靠谱。

The Swift Programming Language 2.1 Examples

源码在 GitHub:https://github.com/gewill/The-Swift-Programming-Language-2.1-Examples

Xcode集成了git,还是非常简单方便的diff/add/commit/push

Playground ->

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
//: Playground - noun: a place where people can play

import UIKit

//: 枚举语法,成员类型为CompassPoint,而非1.2.3.4
enum CompassPoint {
case North
case South
case East
case West
}

enum Something {
case One, Two
}

var directionToHead = CompassPoint.West

//: 相关值(Associated Values)存储相关信息如何条形码和二维码相关信息

enum BarCode {
case UPCA(Int, Int, Int, Int)
case QRCode(String)
}

var productBarcode = BarCode.UPCA(8, 21212, 3223, 3)
productBarcode = BarCode.QRCode("AAAAAAAAA")

//: Switch 可以定义相关值的变量或常量的名称
switch productBarcode {
case .UPCA(let a, let b, let c, let d):
print("\(a + b + c + d)")
case .QRCode(let e):
print(e)
}

//: 原始值(Raw Values)
enum ASCIIControlCharacter: Character {
case Tab = "\t"
case LineFeed = "\n"
case CarriageReturn = "\r"
}
print(ASCIIControlCharacter.Tab)
print(ASCIIControlCharacter.Tab.rawValue)

//: 隐式赋值0...n,但仅仅是原始值
enum Planet: Int {
case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Nepture
}

print(Planet.Earth)
print(Planet.Earth.rawValue)
let possiblePlanet = Planet(rawValue: 4)
let possiblePlanet1 = Planet(rawValue: 9)


//: 递归枚举(Recursive Enumerations)
indirect enum AE {
case Number(Int)
case Addition(AE, AE)
case Multiplicaiton(AE, AE)
}

//: 函数也是一个递归函数
func evaluate(expression: AE) -> Int {
switch expression {
case .Number(let value):
return value
case .Addition(let left, let right):
return evaluate(left) + evaluate(right)
case .Multiplicaiton(let left, let right):
return evaluate(left) * evaluate(right)
}
}

//: 计算 5 + (4 * 7)
let exp = AE.Addition(AE.Number(5), AE.Multiplicaiton(AE.Number(4), AE.Number(7)))
print(evaluate(exp))


综合实现对比了一下委托和通知,Target-Action是更直接的方式。
check phone number

1. 委托

UITextFieldDelegate,只能判断开始编辑那一刻的状态。
optional public func textFieldDidBeginEditing(textField: UITextField)

// became first responder

或者试试这个,下面这个委托实现起来有些啰嗦:正常输入需要加一个字符判断,但是用户按下删除键时 string为””,需要减一个字符来判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//MARK: - phoneNumberTextField delegate
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
//TODO: - 检测电话号码注册与非
if textField.text != nil && string != ""{
let newString = textField.text! + string
if isPhoneNumberAvailable(newString) {
sendVerificationCodeButton.enabled = true
} else {
sendVerificationCodeButton.enabled = false
sendVerificationCodeButton.setTitleColor(UIColor ( red: 0.4, green: 0.4, blue: 0.4, alpha: 1.0 ), forState: UIControlState.Disabled)

}
} else if textField.text != nil && string == "" {
let newString = String(textField.text!.characters.dropLast())
if isPhoneNumberAvailable(newString) {
sendVerificationCodeButton.enabled = true
} else {
sendVerificationCodeButton.enabled = false
}
}
return true

}

但是如此还不行,如果光标移到不在尾部,上面的代码逻辑就不对。还要利用好range。但是这里还是将textField.text!转化为NSString,因为public mutating func replaceRange(subRange: Range<Index>, with newElements: String)这里的 Range<Index>没有搞明白,所以就是去了Unicode 的支持。这里是电话号码也就不影响使用。

参考:http://stackoverflow.com/questions/25138339/nsrange-to-rangestring-index

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//MARK: - phoneNumberTextField delegate
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
//TODO: - 检测电话号码注册与非
if textField.text != nil {

let newString = (textField.text! as NSString).stringByReplacingCharactersInRange(range, withString: string)
if isPhoneNumberAvailable(newString) {
sendVerificationCodeButton.enabled = true
} else {
sendVerificationCodeButton.enabled = false
self.sendVerificationCodeButton.setTitle("发送验证码", forState: UIControlState.Disabled)
}
}
return true
}

2. 注册通知

最为方便,因为我们并不关心 UITextFieldDelegate中新字符string的值

1
public let UITextFieldTextDidChangeNotification: String
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
override func viewDidLoad() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "phoneNumberDidChange:", name: UITextFieldTextDidChangeNotification, object: nil)
}

override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}


//MARK: - 检测电话号码是否正确更改发送验证码状态
func phoneNumberDidChange(aNotification: NSNotification) {
if phoneNumberTextField.isFirstResponder() {
if isPhoneNumberAvailable() {
sendVerificationCodeButton.enabled = true
sendVerificationCodeButton.backgroundColor = Color.colorWithHex("#15C76D")
} else {
sendVerificationCodeButton.enabled = false
self.sendVerificationCodeButton.backgroundColor = UIColor.lightGrayColor()
self.sendVerificationCodeButton.titleLabel?.text = "发送验证码"
}
}
}

3. UIControl 的 Target-Action

由于public class UITextField : UIControl, UITextInput, NSCoding之前没有注意,这里有个最简单直接方法:

- addTarget:action:forControlEvents:或者 IB 添加 Action
EditingChanged: UIControlEvents

1
2
3
4
5
6
7
8
9
10
11
//MARK: - 检测电话号码是否正确更改发送验证码状态
@IBAction func phoneNumberDidChange(sender: UITextField) {
if isPhoneNumberAvailable() {
sendVerificationCodeButton.enabled = true
sendVerificationCodeButton.backgroundColor = Color.colorWithHex("#15C76D")
} else {
sendVerificationCodeButton.enabled = false
self.sendVerificationCodeButton.backgroundColor = UIColor.lightGrayColor()
self.sendVerificationCodeButton.titleLabel?.text = "发送验证码"
}
}

附UIControlEvents:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct UIControlEvents : OptionSetType {
init(rawValue rawValue: UInt)
static var TouchDown: UIControlEvents { get }
static var TouchDownRepeat: UIControlEvents { get }
static var TouchDragInside: UIControlEvents { get }
static var TouchDragOutside: UIControlEvents { get }
static var TouchDragEnter: UIControlEvents { get }
static var TouchDragExit: UIControlEvents { get }
static var TouchUpInside: UIControlEvents { get }
static var TouchUpOutside: UIControlEvents { get }
static var TouchCancel: UIControlEvents { get }
static var ValueChanged: UIControlEvents { get }
static var PrimaryActionTriggered: UIControlEvents { get }
static var EditingDidBegin: UIControlEvents { get }
static var EditingChanged: UIControlEvents { get }
static var EditingDidEnd: UIControlEvents { get }
static var EditingDidEndOnExit: UIControlEvents { get }
static var AllTouchEvents: UIControlEvents { get }
static var AllEditingEvents: UIControlEvents { get }
static var ApplicationReserved: UIControlEvents { get }
static var SystemReserved: UIControlEvents { get }
static var AllEvents: UIControlEvents { get }
}

4. 计时器实现延迟发送验证码

延迟60秒后,可重发发送验证码。使用NSTimer来实现,有个小小的 bug,就是第一次按下发送键,界面卡顿,从59秒起算,以后是60秒起算。以下是完整的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

import UIKit

class SignupViewController: BaseViewController {

var count = 60
var myTimer: NSTimer?

@IBOutlet weak var userNameTextField: UITextField!
@IBOutlet weak var phoneNumberTextField: UITextField!
@IBOutlet weak var VerificationCodeTextField: UITextField!
@IBOutlet weak var password0TextField: UITextField!
@IBOutlet weak var password1TextField: UITextField!

@IBOutlet weak var signupButton: UIButton!
@IBOutlet weak var sendVerificationCodeButton: UIButton!

@IBAction func signupButtonClick(sender: AnyObject) {

}

@IBAction func sendVerificationCodeButtonClick(sender: AnyObject) {
//TODO: - 添加发送验证码模块
sendVerificationCodeButton.enabled = false

myTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "countDownTick", userInfo: nil, repeats: true)
count = 60

}

override func viewDidLoad() {
super.viewDidLoad()

self.navigationItem.title = "注册"
self.setupBackButton()

self.automaticallyAdjustsScrollViewInsets = false

signupButton.layer.cornerRadius = 35.0 / 2
signupButton.backgroundColor = Color.colorWithHex("#15C76D")


NSNotificationCenter.defaultCenter().addObserver(self, selector: "phoneNumberDidChange:", name: UITextFieldTextDidChangeNotification, object: nil)

sendVerificationCodeButton.enabled = false
sendVerificationCodeButton.setTitleColor(UIColor ( red: 0.4, green: 0.4, blue: 0.4, alpha: 1.0 ), forState: UIControlState.Disabled)
}

override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}

//MARK: - 检测电话号码格式是非正确
func isPhoneNumberAvailable() -> Bool {
let phoneNumber = phoneNumberTextField.text
let mobile = "^1(3[0-9]|5[0-35-9]|8[025-9])\\d{8}$"
let CM = "^1(34[0-8]|(3[5-9]|5[017-9]|8[278])\\d)\\d{7}$"
let CU = "^1(3[0-2]|5[256]|8[56])\\d{8}$"
let CT = "^1((33|53|8[09])[0-9]|349)\\d{7}$"
let regextestmobile = NSPredicate(format: "SELF MATCHES %@",mobile)
let regextestcm = NSPredicate(format: "SELF MATCHES %@",CM )
let regextestcu = NSPredicate(format: "SELF MATCHES %@" ,CU)
let regextestct = NSPredicate(format: "SELF MATCHES %@" ,CT)
if ((regextestmobile.evaluateWithObject(phoneNumber) == true)
|| (regextestcm.evaluateWithObject(phoneNumber) == true)
|| (regextestct.evaluateWithObject(phoneNumber) == true)
|| (regextestcu.evaluateWithObject(phoneNumber) == true))
{
return true
}
else
{
return false
}
}



//MARK: - 检测电话号码是否正确更改发送验证码状态
func phoneNumberDidChange(aNotification: NSNotification) {
if phoneNumberTextField.isFirstResponder() {
if isPhoneNumberAvailable() {
sendVerificationCodeButton.enabled = true
sendVerificationCodeButton.backgroundColor = Color.colorWithHex("#15C76D")
} else {
sendVerificationCodeButton.enabled = false
self.sendVerificationCodeButton.backgroundColor = UIColor.lightGrayColor()
self.sendVerificationCodeButton.titleLabel?.text = "发送验证码"
}
}
}

//MARK: -
func countDownTick() {

if count == 1 {
myTimer?.invalidate()
self.sendVerificationCodeButton.enabled = true
self.sendVerificationCodeButton.setTitle("发送验证码", forState: UIControlState.Normal)
count = 61
}

count--
self.sendVerificationCodeButton.setTitle("\(count)秒后重发", forState: UIControlState.Disabled)
}

}
0%