[Swift实际操作]九、完整实例-(7)登录页面:创建自定义视图及相关组件Swift实际操作
本文将开始创建登录页面,首先创建该页面所需的一些自定义组件:
做为登录按钮的自定义视图对象。
在【RegLogin】组的名称上点击鼠标右键,打开右键菜单。
【New File】->【Cocoa Touch Class】创建新文件【RegButton.swift】
Name:RegButton
Subclass:ShadowView
Language:Swift
1 import UIKit 2 3 class RegButton: ShadowView { 4 //给类添加一个按钮类型的属性 5 var bt : UIButton! 6 //首先重写父类的初始化方法 7 override init(frame: CGRect) { 8 super.init(frame: frame) 9 //设置字第个一视图的背景颜色 10 self.backgroundColor = UIColor.white 11 //设置字第个一视图的圆角半径 12 self.cornerRadius = 4.0 13 //设置字第个一视图的投影半径 14 self.shadowRadius = 2.0 15 //设置投影的偏移 16 self.shadowOffset = CGSize(width: 0, height: 1) 17 //设置阴影的颜色 18 self.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 126.0/255) 19 //初始化一个指定显示区域的按钮对象 20 let btFrame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height) 21 //设置按钮的字体 22 bt = UIButton(frame: btFrame) 23 bt.titleLabel?.font = UIFont(name: "PingFang SC", size: 14)! 24 //设置按钮在正常状态的标题颜色 25 bt.setTitleColor(.white, for: .normal) 26 //设置按钮在失效状态的标题颜色 27 bt.setTitleColor(UIColor(red: 230.0/255, green: 230.0/255, blue: 230.0/255, alpha: 1.0), for: .disabled) 28 //设置按钮的标题 29 bt.setTitle("", for: .normal) 30 //设置按钮的背景颜色 31 bt.backgroundColor = .clear 32 //将按钮添加到自定义视图中 33 self.addSubview(bt) 34 //调用自定义视图对象的实例方法, 35 //设置当前视图出于失效状态 36 self.deActive() 37 } 38 //添加一个方法,用来设置当前的自定义视图的状态为激活状态, 39 //从而允许用户点击自动定义视图中的按钮对象 40 func active() 41 { 42 //首先使按钮出于正常状态 43 self.bt.isEnabled = true 44 //按钮正常状态下的背景颜色 45 self.backgroundColor = UIColor(red: 255.0/255, green: 89.0/255, blue: 95.0/255, alpha: 1.0) 46 //正常状态下的标题颜色 47 self.bt.titleLabel?.textColor = UIColor.white 48 } 49 //添加一个方法,用来设置当前的自定义视图的状态为激活状态, 50 //从而不允许用户点击自动定义视图中的按钮对象 51 func deActive() 52 { 53 54 //首先使按钮出于失效状态 55 self.bt.isEnabled = false 56 //按钮失效状态下的背景颜色 57 self.backgroundColor = UIColor.white 58 } 59 //最后添加一个必须实现的初始化方法 60 required init?(coder aDecoder: NSCoder) { 61 fatalError("init(coder:) has not been implemented") 62 } 63 }
接着创建一个针对图像类的扩展。
【New File】->【Swift File】创建新文件【ExtensionUIImage.swift】
接着开始编写代码,完成对图像类的扩展。
1 import UIKit 2 //创建一个图像类的扩展 3 extension UIImage 4 { 5 //添加一个扩展方法,该方法被用于修改文本框右侧的删除图标的颜色 6 func blendColor(_ color: UIColor, blendMode: CGBlendMode) -> UIImage 7 { 8 //获得当前图片的显示区域 9 let rect = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height) 10 //获得当前的图形上下文。 11 //其中第一个参数表示画布的大小 12 //第二个参数表示是否透明图片 13 //第三个参数表示图片的比例 14 UIGraphicsBeginImageContextWithOptions(size, false, scale) 15 //首先将指定的颜色设置为填充色。 16 color.setFill() 17 //然后将填充色填充在指定的区域内。 18 UIRectFill(rect) 19 //最后将指定区域的内容,绘制在当前的图形上下文。 20 draw(in: rect, blendMode: blendMode, alpha: 1.0) 21 //从当前的图形上下文中,获得填充后的图片,并关闭图形上下文。 22 let blendedImage = UIGraphicsGetImageFromCurrentImageContext() 23 UIGraphicsEndImageContext() 24 //最后在方法的末尾,返回图形上下文中获得的图片。 25 return blendedImage! 26 } 27 }
使用键盘上的快捷键【Command】+【N】,继续创建一个类文件。
接着创建一个针对图像类的扩展。
【New File】->【Cocoa Touch Class】创建新文件【UserInfo.swift】
Name:UserInfo
Subclass:NSObjecct
Language:Swift
该类将用来在本地存储用户信息
1 import UIKit 2 import Foundation 3 //使类遵循编码协议,它可以将类的实例转化为序列化的数据进行存储。 4 class UserInfo : NSObject, NSCoding { 5 //添加属性:表示用户的考试类型 6 var myKeMu : PaperType = .ACT 7 //添加属性:用户是否为海外用户 8 var isHaiWai : Bool = false 9 //手机号码 10 var phone : String = "" 11 //邮箱 12 var email : String = "" 13 //密码 14 var password : String = "" 15 //昵称 16 var nickname : String = "" 17 //验证码 18 var verifyCode : String = "" 19 //头像图片 20 var avatar : UIImage = UIImage() 21 //是否参加过考试 22 var isTested : Bool = false 23 //头像路径 24 var head_img : String = "" 25 //年级 26 var grade : Int = 1 27 //生日 28 var birthday = "" 29 //用户的唯一标志符 30 var id = 0 31 //性别 32 var sex = 0 33 //老师 34 var teacher = "" 35 //用户考试类型 36 var exam_type = 0 37 //状态 38 var status = 0 39 //当前总考分 40 var current_score : Int = 0 //相当于服务器中的current_score 41 //英语 42 var english : Int = 0 //相当于服务器中的english 43 //数学 44 var math : Int = 0 //相当于服务器中的math 45 //阅读 46 var reading : Int = 0 //相当于服务器中的reading 47 //科学 48 var science : Int = 0 //相当于服务器中的science 49 //写作 50 var writing : Int = 0 //相当于服务器中的writing 51 52 var expect_english : Int = 0 53 var expect_reading : Int = 0 54 var expect_science : Int = 0 55 var expect_score : Int = 0 56 var expect_writing : Int = 0 57 var expect_math : Int = 0 58 59 //实现协议中的方法 60 //用来将用户信息进行编码 61 func encode(with aCoder: NSCoder) 62 { 63 //首先对用户的手机、邮箱、密码、昵称、和是否参考过考试等属性进行编码 64 //并设置对应的键名 65 aCoder.encode(self.phone, forKey: "phone") 66 aCoder.encode(self.email, forKey: "email") 67 aCoder.encode(self.password, forKey: "password") 68 aCoder.encode(self.nickname, forKey: "nickname") 69 aCoder.encode(self.isTested, forKey: "isTested") 70 //接着对另外八个属性进行编码 71 aCoder.encode(self.head_img, forKey: "head_img") 72 aCoder.encode(self.grade, forKey: "grade") 73 aCoder.encode(self.birthday, forKey: "birthday") 74 aCoder.encode(self.id, forKey: "id") 75 aCoder.encode(self.sex, forKey: "sex") 76 aCoder.encode(self.teacher, forKey: "teacher") 77 aCoder.encode(self.exam_type, forKey: "exam_type") 78 aCoder.encode(self.status, forKey: "status") 79 //最后对剩余的六个属性进行编码 80 aCoder.encode(self.current_score, forKey: "current_score") 81 aCoder.encode(self.english, forKey: "english") 82 aCoder.encode(self.math, forKey: "math") 83 aCoder.encode(self.reading, forKey: "reading") 84 aCoder.encode(self.science, forKey: "science") 85 aCoder.encode(self.writing, forKey: "writing") 86 87 aCoder.encode(self.expect_english, forKey: "expect_english") 88 aCoder.encode(self.expect_reading, forKey: "expect_reading") 89 aCoder.encode(self.expect_science, forKey: "expect_science") 90 aCoder.encode(self.expect_score, forKey: "expect_score") 91 aCoder.encode(self.expect_writing, forKey: "expect_writing") 92 aCoder.encode(self.expect_math, forKey: "expect_math") 93 } 94 //实现协议中的方法,用来将将过编码的用户信息进行解码 95 required init(coder aDecoder: NSCoder) 96 { 97 super.init() 98 //首先对用户的手机、邮箱、密码、昵称、和是否参考过考试等属性进行解码 99 self.phone = aDecoder.decodeObject(forKey: "phone") as! String 100 self.email = aDecoder.decodeObject(forKey: "email") as! String 101 self.password = aDecoder.decodeObject(forKey: "password") as! String 102 self.nickname = aDecoder.decodeObject(forKey: "nickname") as! String 103 self.nickname = aDecoder.decodeObject(forKey: "nickname") as! String 104 //接着对另外八个属性进行解码 105 self.head_img = aDecoder.decodeObject(forKey: "head_img") as! String 106 self.grade = aDecoder.decodeInteger(forKey: "grade") 107 self.birthday = aDecoder.decodeObject(forKey: "birthday") as! String 108 self.id = aDecoder.decodeInteger(forKey: "id") 109 self.sex = aDecoder.decodeInteger(forKey: "sex") 110 self.teacher = aDecoder.decodeObject(forKey: "teacher") as! String 111 self.exam_type = aDecoder.decodeInteger(forKey: "exam_type") 112 self.status = aDecoder.decodeInteger(forKey: "status") 113 //然后对剩余的六个属性进行解码 114 self.current_score = aDecoder.decodeInteger(forKey: "current_score") 115 self.english = aDecoder.decodeInteger(forKey: "english") 116 self.math = aDecoder.decodeInteger(forKey: "math") 117 self.reading = aDecoder.decodeInteger(forKey: "reading") 118 self.science = aDecoder.decodeInteger(forKey: "science") 119 self.writing = aDecoder.decodeInteger(forKey: "writing") 120 121 self.expect_english = aDecoder.decodeInteger(forKey: "expect_english") 122 self.expect_reading = aDecoder.decodeInteger(forKey: "expect_reading") 123 self.expect_science = aDecoder.decodeInteger(forKey: "expect_science") 124 self.expect_score = aDecoder.decodeInteger(forKey: "expect_score") 125 self.expect_writing = aDecoder.decodeInteger(forKey: "expect_writing") 126 self.expect_math = aDecoder.decodeInteger(forKey: "expect_math") 127 128 129 } 130 //最后添加一个初始化方法,完成用户信息类的创建 131 override init() 132 { 133 134 } 135 136 func setScore(num:Int, score:Int) 137 { 138 switch num { 139 case 0: 140 self.current_score = score 141 case 1: 142 self.english = score 143 case 2: 144 self.math = score 145 case 3: 146 self.reading = score 147 case 4: 148 self.science = score 149 default: break 150 151 } 152 } 153 154 func setSATScore(num:Int, score:Int) 155 { 156 switch num { 157 case 0: 158 self.reading = score 159 case 1: 160 self.math = score 161 case 2: 162 self.writing = score 163 default: break 164 165 } 166 } 167 }
使用键盘上的快捷键【Command】+【N】,继续创建一个类文件。
【New File】->【Swift File】创建新文件【DataUtil.swift】
接着开始编写代码,完成对图像类的扩展。
1 import Foundation 2 import UIKit 3 import Alamofire 4 import Toaster 5 import PKHUD 6 import Qiniu 7 8 class DataUtil 9 { 10 class func hasLogined() -> Bool 11 { 12 return UserDefaults.standard.bool(forKey: "hasLogin") 13 } 14 class func setLogined() 15 { 16 UserDefaults.standard.set(true, forKey: "hasLogin") 17 UserDefaults.standard.synchronize() 18 } 19 class func setLoginOut() 20 { 21 UserDefaults.standard.set(false, forKey: "hasLogin") 22 UserDefaults.standard.synchronize() 23 } 24 25 class func hasShowIntro() -> Bool 26 { 27 return UserDefaults.standard.bool(forKey: "hasShowIntro") 28 } 29 class func setShowInfo() 30 { 31 UserDefaults.standard.set(true, forKey: "hasShowIntro") 32 UserDefaults.standard.synchronize() 33 } 34 35 class func getTabBarHeight(vc:UIViewController) -> CGFloat 36 { 37 return (vc.tabBarController?.tabBar.frame.size.height)! 38 } 39 40 class func setCrtUser(userInfo : UserInfo) 41 { 42 let data = NSMutableData() 43 let archive = NSKeyedArchiver(forWritingWith: data) 44 archive.encode(userInfo, forKey: "crtUserInfo") 45 archive.finishEncoding() 46 47 let filePath = NSHomeDirectory() + "/Documents/crtUserInfo.data" 48 data.write(toFile: filePath, atomically: true) 49 } 50 //添加一个类方法,用来获得归档的用户信息 51 class func getCrtUser() -> UserInfo 52 { 53 //首先获得用户归档文件的存储路径 54 let filePath = NSHomeDirectory() + "/Documents/crtUserInfo.data" 55 //然后加载该路径下的文件,并初始化一个键值解码器 56 let fileData = NSMutableData(contentsOfFile: filePath) 57 let unarchiver = NSKeyedUnarchiver(forReadingWith: fileData! as Data) 58 //根据键名,对指定的归档进行解码 59 let savedUser = unarchiver.decodeObject(forKey: "crtUserInfo") as! UserInfo 60 unarchiver.finishDecoding() 61 //最后返回解码后的用户信息对象 62 return savedUser 63 } 64 //接着添加一个类方法,用来设置用户使用游客身份访问应用程序 65 class func setVisitorLogin(value:Bool) 66 { 67 UserDefaults.standard.set(value, forKey: "IsVisitorLogin") 68 UserDefaults.standard.synchronize() 69 } 70 //添加一个类方法,用来判断用户是否为游客身份 71 class func isVisitorLogin() -> Bool 72 { 73 return UserDefaults.standard.bool(forKey: "IsVisitorLogin") 74 } 75 //添加一个类方法,用来将头像数据存储到沙盒目录 76 class func saveAvarta(data:Data) 77 { 78 let targetPath:String = NSHomeDirectory() + "/Documents/avarta.png" 79 try? data.write(to: URL(fileURLWithPath: targetPath)) 80 } 81 //添加一个类方法,用来读取沙盒中保存的头像 82 class func getAvarta() -> UIImage? 83 { 84 let targetPath:String = NSHomeDirectory() + "/Documents/avarta.png" 85 86 let image = UIImage(contentsOfFile: targetPath) 87 return image 88 } 89 //添加一个类方法,用来给图像视图设置用户的头像 90 class func setAvartaForImageView(imageView : UIImageView) 91 { 92 //首先读取之前保存的用户头像 93 let localImage = getAvarta() 94 //判断当读取的图像不为空时,设置图像视图 95 if(localImage == nil) 96 { 97 //如果没有获得用户头像, 98 let user = getCrtUser() 99 //则从保存的用户信息中,获取用户头像的远程路径 100 let imageUrl = user.head_img 101 //如果远程路径为空,则设置用户头像为默认图片 102 if(imageUrl != "") 103 { 104 //如果远程路径不为空, 105 let url = URL(string: imageUrl) 106 //则初始化一个指定网址的网络请求 107 let request = URLRequest(url: url!) 108 //使用异步的方式,请求远程服务器上的图片资源 109 NSURLConnection.sendAsynchronousRequest(request, queue: OperationQueue.main, completionHandler: {(response:URLResponse?, data:Data?,error:Error?)->Void in 110 //然后返回主线程,对图像视图进行更新 111 DispatchQueue.main.async(execute: { () -> Void in 112 let image = UIImage(data: data!) 113 imageView.image = image 114 }) 115 }) 116 } 117 else 118 { 119 //则设置用户头像为默认图片 120 imageView.image = UIImage(named: "defaultAvarta") 121 } 122 } 123 else 124 { 125 imageView.image = localImage 126 } 127 } 128 //添加一个方法,用来将头像上传至七牛服务器 129 class func uploadAvartaToQiNiu(baseUrl: String, avarta: UIImage) 130 { 131 //初始化一个字符串常量,表示网络请求的接口地址 132 let url = "\(baseUrl)iOS/uploadAvarta.json" 133 //然后开始显示加载动画,并设置弹出框的字体样式。 134 HUD.show(.progress) 135 ToastView.appearance().font = UIFont(name: "PingFang SC", size: 14)! 136 //使用第三方类库,访问指定的网址,并返回Json格式的结果 137 Alamofire.request(url, method: .get).responseJSON 138 { 139 //如果返回成功的结果 140 //则获得返回结果中的编码和详情字段 141 response in 142 print(response) 143 if let json = response.result.value as? [String: Any] 144 { 145 print("JSON: \(json)") 146 let code = json["code"] as? Int ?? 0 147 let detail = json["detail"] as? [String: Any] 148 //设置弹出窗口到屏幕底部的距离 149 ToastView.appearance().bottomOffsetPortrait = 150 150 //如果返回值为0,则表示网络请求成功, 151 if(code == 0) 152 { 153 //获得服务器返回的将要上传至七牛的图片名称和上传口令。 154 let upload_name = detail?["upload_name"] as? String ?? "" 155 //获得服务器返回的将要上传至七牛的上传口令。 156 let upload_token = detail?["upload_token"] as? String ?? "" 157 print(upload_token) 158 //初始化一个七牛上传管理器 159 let upManager = QNUploadManager() 160 //并将图片转换为二进制 161 let data = UIImagePNGRepresentation(avarta) 162 //调用七牛上传管理器的上传方法,开始图片的上传。 163 upManager?.put(data, key: upload_name, token: upload_token, complete:{ (info, key, resp) -> Void in 164 165 print(info) 166 print("----------") 167 print(resp) 168 //如果七牛返回的状态码为200,则表示上传成功。 169 if (info?.statusCode == 200 && resp != nil) 170 { 171 //接着将用户信息进行更新, 172 //首先获得已经保存在沙盒中的当前的用户 173 let userInfo = DataUtil.getCrtUser() 174 //定义一个网络地址,用来获得服务器上的用户信息 175 let url = "\(baseUrl)iOS/headImage.json" 176 //并设置访问时传递的参数,以更新远程服务器上用户的头像信息 177 let parameters = ["head_img": "https://www.cnblogs.com/strengthen/\(upload_name)"] 178 //使用第三方类库,访问指定的网址,并返回Json格式的结果 179 Alamofire.request(url, method: .get, parameters: parameters).responseJSON 180 { 181 response in 182 HUD.hide(animated: true) 183 //对返回的结果进行判断和处理 184 if let json = response.result.value as? [String: Any] 185 { 186 print("JSON: \(json)") 187 //获得服务器返回的状态码 188 let code = json["code"] as? Int ?? 0 189 //如果状态码为0,则表示请求成功,否则弹出失败提示窗口 190 if(code == 0) 191 { 192 //显示弹出窗口,提示图片上传成功。 193 ToastView.appearance().bottomOffsetPortrait = 80 194 Toast(text: "Image was successfully uploaded!").show() 195 //然后获得服务器返回的头像路径, 196 //并更新用户信息中的头像路径。 197 let detail = json["detail"] as? [String: Any] 198 userInfo.head_img = detail?["head_img"] as? String ?? "" 199 //最后更新存储在本地的用户信息。 200 DataUtil.setCrtUser(userInfo: userInfo) 201 DataUtil.saveAvarta(data: data!) 202 } 203 else 204 { 205 let detail = json["detail"] as? String ?? "" 206 ToastView.appearance().bottomOffsetPortrait = 80 207 Toast(text: detail).show() 208 209 } 210 } 211 else 212 { 213 ToastView.appearance().bottomOffsetPortrait = 80 214 Toast(text: "Upload failed!").show() 215 } 216 } 217 } 218 else 219 { 220 HUD.hide(animated: true) 221 } 222 223 }, option: nil) 224 } 225 else 226 { 227 //r如果返回编码的值不为零。则提示编码失败 228 ToastView.appearance().bottomOffsetPortrait = 80 229 //则打开提示窗口,提示上传失败, 230 Toast(text: "Upload failed!").show() 231 //并隐藏加载动画 232 HUD.hide(animated: true) 233 } 234 } 235 else 236 { 237 //如果上传失败, 238 ToastView.appearance().bottomOffsetPortrait = 80 239 //则打开提示窗口,提示上传失败, 240 Toast(text: "Upload failed!").show() 241 //并隐藏加载动画 242 HUD.hide(animated: true) 243 } 244 } 245 } 246 } 247 248 func getKemuBySection(section: String) -> String 249 { 250 if(section == "english") 251 { 252 return "英语" 253 } 254 else if(section == "math") 255 { 256 return "数学" 257 } 258 else if(section == "reading") 259 { 260 return "阅读" 261 } 262 else if(section == "science") 263 { 264 return "科学" 265 } 266 else if(section == "language") 267 { 268 return "语言" 269 } 270 else if(section == "mathc") 271 { 272 return "数学计算器" 273 } 274 else 275 { 276 return section 277 } 278 } 279 280 func getLevel(level: String) -> String 281 { 282 if(level == "E") 283 { 284 return "简单" 285 } 286 else if(level == "M") 287 { 288 return "中等" 289 } 290 else 291 { 292 return "困难" 293 } 294 }