[Swift实际操作]九、完整实例-(8)登录页面:创建自定义表单Row以及控制器
本文将为你演示,如何知错自定义的表单行,表单行包含一个输入框和错误提示标签。
用于登录、注册以及其他需要表单的页面。
【New File】->【Cocoa Touch Class】创建新文件【FormRow.swift】
Name:FormRow
Subclass:UIView
Language:Swift
接着开始编写代码,完成自定义表单行的创建。
1 import UIKit 2 //定义一个枚举类型,表示表单中的输入框的类型,共5种 3 enum FieldType 4 { 5 //手机 6 case Tel 7 //邮箱 8 case Email 9 //密码 10 case Password 11 //验证码 12 case VerifyCode 13 //昵称 14 case NickName 15 } 16 17 //定义一个结构体类型 18 struct RegexHelper 19 { 20 //添加一个正则表达式常量 21 let regex: NSRegularExpression? 22 //并添加一个初始化方法 23 init(_ pattern: String) 24 { 25 //接着通过一个异常捕捉语句, 26 do 27 { 28 //初始化一个正则表达式 29 //并设置进行判断时,不区分英文字母的大小写 30 regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive) 31 } 32 catch 33 { 34 regex = nil 35 } 36 } 37 38 //添加一个方法 39 //使用正则表达式,对字符串进行匹配,并返回匹配的结果 40 func match(_ input: String) -> Bool 41 { 42 //初始化一个范围常量 43 let len = input.characters.count 44 let range = NSMakeRange(0, len) 45 //使用正则表达式,对字符串进行匹配,并返回匹配的结果 46 if let matches = regex?.matches(in: input, 47 options: .reportProgress, 48 range: range) 49 { 50 return matches.count > 0 51 } 52 else 53 { 54 return false 55 } 56 } 57 } 58 59 //给当前的类添加一个文本框协议,用来实现对文本框输入时检测 60 class FormRow: UIView, UITextFieldDelegate { 61 //初始化一个布尔变量,用来标识文本框的内容是否符合规则 62 var isPass : Bool = false 63 //依次初始化4个组件:1、显示文本框的标题 64 var labelTitle : UILabel! 65 //依次初始化4个组件:2、显示错误提示信息 66 var labelError : UILabel! 67 //依次初始化4个组件:3、输入框 68 var inputField : UITextField! 69 //依次初始化4个组件:4、分割线 70 var line : UIView! 71 //初始化一个视图控制器属性, 72 //该属性的类文件将在后面创建 73 weak var controller : BaseLoginRegViewController! 74 //第一个属性:表示文本框的默认类型 75 var valueType : FieldType = FieldType.Tel //0:tel, 1:email, 2:password 3:verifycode 4:username 76 //第二个属性:表示正确的验证码 77 var correctVerifyCode = "" 78 //第三个属性:表示错误提示标签的显示区域 79 var frameError : CGRect! 80 81 //重写视图类的初始化方法 82 override init(frame: CGRect) { 83 super.init(frame: frame) 84 //初始化一个指定显示区域的标签对象 85 let frameTitle = CGRect(x: 20, y: 0, width: frame.size.width-40, height: 20) 86 labelTitle = UILabel(frame: frameTitle) 87 //设置标签的文字颜色 88 labelTitle.textColor = .white 89 //设置标签的字体属性 90 labelTitle.font = UIFont(name: "PingFang SC", size: 14) 91 //并将标签添加到自定义视图 92 self.addSubview(labelTitle) 93 //初始化一个指定显示区域的文本输入框 94 let frameInput = CGRect(x: 20, y: 20, width: frame.size.width-40, height: 40) 95 inputField = UITextField(frame: frameInput) 96 //设置输入框的代理 97 inputField.delegate = self 98 //设置输入框的字体颜色 99 inputField.textColor = .white 100 //设置输入框的清除按钮的模式 101 inputField.clearButtonMode = .whileEditing 102 //设置输入框的字体属性 103 inputField.font = UIFont(name: "PingFang SC", size: 17) 104 //并将输入框添加到自定义视图 105 self.addSubview(inputField) 106 //初始化一个具有白色背景的视图对象,作为表单行之间的分割线 107 line = UIView(frame: CGRect(x: 20, y: 64, width: frame.size.width-40, height: 1)) 108 line.backgroundColor = UIColor.white 109 self.addSubview(line) 110 //初始化一个标签对象,用来在用户输入错误时显示提示信息 111 frameError = CGRect(x: 20, y: 69, width: frame.size.width-40, height: 14) 112 labelError = UILabel(frame: frameError) 113 //设置标签的文字颜色 114 labelError.textColor = UIColor(red: 255.0/255, green: 89.0/255, blue: 95.0/255, alpha: 1.0) 115 //设置标签的字体属性 116 labelError.font = UIFont(name: "PingFang SC", size: 10)! 117 //默认情况下,错误标签处于隐藏状态 118 labelError.isHidden = true 119 self.addSubview(labelError) 120 //添加一个通知,用来检测输入框种的内容发生变化时的事件 121 NotificationCenter.default.addObserver(self, 122 selector: #selector(FormRow.enteringContent(_:)), 123 name: NSNotification.Name.UITextFieldTextDidChange, 124 object: nil) 125 } 126 127 //添加一个实例方法,用来获得输入框中的文字 128 func getValue() -> String 129 { 130 return inputField.text! 131 } 132 133 //添加一个方法,用来设置自定义视图中的输入框的类型 134 func setValueType(type : FieldType) 135 { 136 self.valueType = type 137 //当类型为电话号码时 138 if(type == .Tel) 139 { 140 //设置输入框的键盘类型 141 self.inputField.keyboardType = UIKeyboardType.phonePad 142 //设置输入框的标题文字 143 self.labelTitle.text = "电话号码" 144 145 } 146 //当类型为邮箱时 147 else if(type == .Email) 148 { 149 //设置输入框的键盘类型 150 self.inputField.keyboardType = UIKeyboardType.emailAddress 151 //设置输入框的标题文字 152 self.labelTitle.text = "邮箱地址" 153 } 154 //当类型为密码时 155 else if(type == .Password) 156 { 157 //设置输入框的键盘类型 158 self.inputField.keyboardType = UIKeyboardType.URL 159 //设置密文输入 160 self.inputField.isSecureTextEntry = true 161 //设置输入框的标题文字 162 self.labelTitle.text = "密码" 163 } 164 //当类型为验证码时 165 else if(type == .VerifyCode) 166 { 167 //设置输入框的键盘类型 168 self.inputField.keyboardType = UIKeyboardType.numberPad 169 //设置输入框的标题文字 170 self.labelTitle.text = "验证码" 171 } 172 //当类型为昵称时 173 else if(type == .NickName) 174 { 175 //设置输入框的键盘类型 176 self.inputField.keyboardType = UIKeyboardType.namePhonePad 177 //设置输入框的标题文字 178 self.labelTitle.text = "用户昵称" 179 } 180 } 181 182 //添加一个方法,用来判断输入框中的内容是否为空 183 func checkIsNotBlank() 184 { 185 if self.getValue() != "" 186 { 187 //当值不为空时,设置布尔属性的值为真 188 self.isPass = true 189 //接着调用控制器对象的校验表单功能 190 self.controller.checkForm() 191 } 192 else 193 { 194 //当值为空时,设置布尔属性的值为真, 195 self.isPass = false 196 //接着调用控制器对象的校验表单功能 197 self.controller.checkForm() 198 } 199 } 200 201 //添加一个方法,用来对输入框的内容进行检验, 202 //并返回布尔类型的结果 203 func checkValue() -> Bool 204 { 205 //处理类型为电话号码时的情况 206 if(self.valueType == .Tel) 207 { 208 //设置用来匹配电话号码的正则表达式字符串 209 let pattern = "^1[0-9]{10}$" 210 //并初始化一个正则匹配的结构体对象 211 let matcher = RegexHelper(pattern) 212 //对输入框中的内容进行匹配, 213 if !matcher.match(self.getValue()) 214 { 215 //并返回输入失败时的结果 216 self.setError(info: "电话号码输入有误") 217 return false 218 } 219 } 220 //处理类型为电子邮箱时的情况 221 else if(self.valueType == .Email) 222 { 223 //设置用来匹配电子邮箱的正则表达式字符串 224 let pattern = "^([a-z0-9_\\.-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$" 225 //并初始化一个正则匹配的结构体对象 226 let matcher = RegexHelper(pattern) 227 //对输入框中的内容进行匹配, 228 let value = self.getValue() 229 if !matcher.match(value) 230 { 231 //并返回输入失败时的结果 232 self.setError(info: "邮箱地址输入有误") 233 return false 234 } 235 } 236 //处理类型为密码时的情况 237 else if(self.valueType == .Password) 238 { 239 //设置用来匹配密码的正则表达式字符串 240 let pattern = "^[a-z0-9_-]{6,16}$" 241 //并初始化一个正则匹配的结构体对象 242 let matcher = RegexHelper(pattern) 243 //对输入框中的内容进行匹配, 244 if !matcher.match(self.getValue()) 245 { 246 //并返回输入失败时的结果 247 self.setError(info: "密码输入有误") 248 return false 249 } 250 } 251 //处理类型为验证码时的情况 252 else if(self.valueType == .VerifyCode) 253 { 254 //设置用来匹配验证码的正则表达式字符串 255 let pattern = "^[0-9]{6,6}$" 256 //并初始化一个正则匹配的结构体对象 257 let matcher = RegexHelper(pattern) 258 //对输入框中的内容进行匹配, 259 if !matcher.match(self.getValue()) 260 { 261 //并返回输入失败时的结果 262 self.setError(info: "验证码输入有误") 263 return false 264 } 265 } 266 //处理类型为昵称时的情况 267 else if(self.valueType == .NickName) 268 { 269 //设置用来匹配昵称的正则表达式字符串 270 let pattern = "^[0-9_-a-zA-Z\\u4E00-\\u9FA5]{2,100}$" 271 //并初始化一个正则匹配的结构体对象 272 let matcher = RegexHelper(pattern) 273 //对输入框中的内容进行匹配, 274 if !matcher.match(self.getValue()) 275 { 276 //并返回输入失败时的结果 277 self.setError(info: "用户名称输入有误") 278 return false 279 } 280 } 281 //当执行到方法的末尾时,表示匹配成功, 282 //此时设置自定义表单行的状态, 283 self.setSuccess() 284 //并返回值为真的结果 285 return true 286 } 287 288 //添加一个方法,用来设置自定义表单行在匹配错误时的状态 289 func setError(info : String) 290 { 291 //设置错误标签的内容 292 self.isPass = false 293 self.labelError.text = info 294 //并显示错误标签 295 self.labelError.isHidden = false 296 //由于错误标签是通过动画来显示的, 297 //所以在此对错误标签的显示区域和透明度进行初始化 298 let rect = CGRect(x: frameError.origin.x, y: frameError.origin.y - 20, width: frameError.size.width, height: frameError.size.height) 299 self.labelError.frame = rect 300 self.labelError.layer.opacity = 0.0 301 //设置标题颜色 302 self.labelTitle.textColor = .white 303 //设置输入框文字颜色 304 self.inputField.textColor = .white 305 //设置分割线的背景颜色 306 self.line.backgroundColor = .white 307 //使用视图的类方法,开始执行一段动画 308 UIView.beginAnimations("switchToDaLu", context: nil) 309 //并设置动画的时长为0.6秒 310 UIView.setAnimationDuration(0.6) 311 //在动画块中,更改动画标签的透明度 312 self.labelError.layer.opacity = 1.0 313 //在动画块中,更改动画标签的显示区域 314 self.labelError.frame = self.frameError 315 //修改标题颜色 316 self.labelTitle.textColor = self.labelError.textColor 317 //修改输入框文字颜色 318 self.inputField.textColor = self.labelError.textColor 319 //修改分割线的背景颜色 320 self.line.backgroundColor = self.labelError.textColor 321 //以上外观的变化,都以动画的形式,在0.63秒之内完成。 322 UIView.commitAnimations() 323 //最后结束视图的动画块 324 self.controller.checkForm() 325 } 326 327 //添加一个方法,用来设置自定义表单行在匹配成功时的状态 328 func setSuccess() 329 { 330 //在上一个方法种,是通过视图动画块的方式创建动画的, 331 //在当前方法种,你将通过闭包的方式,创建一个动画。 332 //同时在动画结束时,隐藏错误标签 333 UIView.animate(withDuration: 0.6, delay: 0.0, options: .curveLinear, animations: { 334 335 self.isPass = true 336 //初始化一个范围对象,作为错误标签的起始位置 337 let rect = CGRect(x: self.frameError.origin.x, y: self.frameError.origin.y - 20, width: self.frameError.size.width, height: self.frameError.size.height) 338 //将错误标签移至起始位置,并设置不透明度为0 339 self.labelError.frame = rect 340 self.labelError.layer.opacity = 0.0 341 //修改标题颜色 342 self.labelTitle.textColor = .white 343 //设置输入框文字颜色 344 self.inputField.textColor = .white 345 //设置分割线的背景颜色 346 self.line.backgroundColor = .white 347 //对控制器种的所有表单行进行校验 348 self.controller.checkForm() 349 350 }, completion: { finished in 351 //同时在动画结束时,隐藏错误标签 352 self.labelError.isHidden = true 353 }) 354 } 355 356 //添加一个协议方法,用来监听输入框开始编辑的事件 357 func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { 358 359 self.controller.focusedFieldType = self.valueType 360 361 return true 362 } 363 364 //继续添加一个来自协议的方法,用来监听输入框的内容发生变化时的事件 365 func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { 366 //将输入框右侧的删除图标的颜色进行修改,将其颜色变亮。 367 let count = inputField.subviews.count 368 369 if count >= 2 370 { 371 let bt = inputField.subviews[1] as! UIButton 372 bt.imageView?.image = bt.imageView?.image?.blendColor(.white, blendMode: .destinationIn) 373 } 374 //当类型为密码时,最多允许输入11个字符 375 var maxNum = 11 376 if(self.valueType == .Password) 377 { 378 maxNum = 16 379 } 380 //当类型为验证码时,最多允许输入6个字符 381 else if(self.valueType == .VerifyCode) 382 { 383 maxNum = 6 384 } 385 //当类型为昵称时,最多允许输入100个字符 386 else if(self.valueType == .NickName) 387 { 388 maxNum = 100 389 } 390 //当类型为邮箱时,最多允许输入100个字符 391 else if(self.valueType == .Email) 392 { 393 maxNum = 100 394 } 395 //获得输入框中已有的内容 396 let txt = textField.text 397 let str = "\(txt!)\(string)" 398 //判断当输入框种的已有内容的长度大于或等于最大值时, 399 //输入框不再接入字符的输入 400 if str.lengthOfBytes(using: .utf8) <= maxNum 401 { 402 return true 403 } 404 else 405 { 406 return false 407 } 408 } 409 410 //添加一个方法,用来响应通知。 411 //当收到输入框中的内容发生变化的通知时, 412 //对输入框的内容是否为空进行校验 413 func enteringContent(_ notification: Notification?) 414 { 415 self.checkIsNotBlank() 416 } 417 //添加一个必须实现的初始化方法 418 required init?(coder aDecoder: NSCoder) { 419 fatalError("init(coder:) has not been implemented") 420 } 421 422 //最后添加一个解析方法, 423 //当自定义表单行对象被从内存清除之前,移除刚刚创建的通知。 424 deinit { 425 NotificationCenter.default.removeObserver(self) 426 } 427 }
【New File】->【Cocoa Touch Class】创建新文件【BaseLoginRegViewController.swift】
Name:BaseLoginRegviewController
Subclass:BaseViewController
Language:Swift
接着开始编码,完成基类的创建
1 import UIKit 2 3 class BaseLoginRegViewController: BaseViewController { 4 //首先添加一个属性,表示当前处于焦点状态时的表单行的类型。 5 var focusedFieldType : FieldType = .Password 6 override func viewDidLoad() { 7 super.viewDidLoad() 8 // Do any additional setup after loading the view. 9 //隐藏顶部的导航栏 10 self.navigationController?.setNavigationBarHidden(true, animated: false) 11 //隐藏左上角的关闭按钮 12 self.dismissBt.isHidden = true 13 //给根视图添加一个手势,当根视图被点击时,隐藏已经打开的键盘 14 self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(BaseLoginRegViewController.hideKeyboard))) 15 } 16 17 //添加一个方法,来响应根视图的手势事件 18 func hideKeyboard() 19 { 20 self.view.endEditing(true) 21 } 22 23 //最后添加一个方法,用来对页面中的所有表单进行检验, 24 //该方法将在子类中进行重写 25 func checkForm() 26 { 27 28 } 29 30 override func didReceiveMemoryWarning() { 31 super.didReceiveMemoryWarning() 32 // Dispose of any resources that can be recreated. 33 } 34 }