寫著玩兒的小程序,繼續學習swift.運行效果+代碼+知識點總結
class Canvas:UIView{ //負責線條的生成、操作與管理 let pathCreator:PathCreator //是否處于擦除狀態 var isInErasering:Bool //橡皮擦視圖 let eraserView:UIView override init(frame: CGRect) { isInErasering = false pathCreator = PathCreator() eraserView = UIView.init() eraserView.frame = CGRect(x: 0, y: 0, width: 10, height: 10) eraserView.backgroundColor = UIColor.white eraserView.alpha = 0 super.init(frame: frame) self.backgroundColor = UIColor.black self.addSubview(eraserView) let revokeBut = UIButton(type: UIButtonType.system) revokeBut.frame = CGRect(x: 20, y: 20, width: 80, height: 30) revokeBut.setTitle("撤銷", for: UIControlState.normal) revokeBut.addTarget(self, action: #selector(revokeButClick), for: UIControlEvents.touchUpInside) self.addSubview(revokeBut) let cleanBut = UIButton(type: UIButtonType.system) cleanBut.frame = CGRect(x: 110, y: 20, width: 80, height: 30) cleanBut.setTitle("清空", for: UIControlState.normal) cleanBut.addTarget(self, action: #selector(cleanButClick), for: UIControlEvents.touchUpInside) self.addSubview(cleanBut) let eraserBut = UIButton(type: UIButtonType.system) eraserBut.frame = CGRect(x: 200, y: 20, width:80, height: 30) eraserBut.setTitle("橡皮", for: UIControlState.normal) eraserBut.setTitle("畫筆", for: UIControlState.selected) eraserBut.addTarget(self, action: #selector(eraserButClick(but:)), for: UIControlEvents.touchUpInside) self.addSubview(eraserBut) let ges = UipanGestureRecognizer(target: self, action:#selector(handleGes(ges:))) ges.maximumNumberOfTouches = 1 self.addGestureRecognizer(ges) } required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override public func layoutSubviews() { } @objc PRivate func handleGes(ges:UIPanGestureRecognizer) -> Void { let point = ges.location(in: self) switch ges.state { case UIGestureRecognizerState.began: if isInErasering { //擦除狀態,顯示出橡皮擦 eraserView.alpha = 1 eraserView.center = point } //生成新的一筆 pathCreator.addNewPath(to: point,isEraser: isInErasering) self.setNeedsDisplay() case UIGestureRecognizerState.changed: if isInErasering { //移動橡皮擦 eraserView.center = ges.location(in: self) } //更新當前筆畫路徑 pathCreator.addLineForCurrentPath(to: point,isEraser:isInErasering) self.setNeedsDisplay() case UIGestureRecognizerState.ended: if isInErasering { //擦除狀態,隱藏橡皮擦 eraserView.alpha = 0 eraserView.center = ges.location(in: self) } //更新當前筆畫路徑 pathCreator.addLineForCurrentPath(to: point,isEraser: isInErasering) self.setNeedsDisplay() case UIGestureRecognizerState.cancelled: print("cancel") case UIGestureRecognizerState.failed: print("fail") default: return } } override public func draw(_ rect: CGRect) { //畫線 pathCreator.drawPaths() } @objc private func revokeButClick()->Void{ //撤銷操作 pathCreator.revoke() self.setNeedsDisplay() } @objc private func cleanButClick()->Void{ //清空操作 pathCreator.clean() self.setNeedsDisplay() } @objc private func eraserButClick(but:UIButton)->Void{ //切換畫圖與擦除狀態 if but.isSelected { but.isSelected = false isInErasering = false }else{ but.isSelected = true isInErasering = true } }}PathCreator:具體線條繪制、管理
//每條子線段信息struct BezierInfo{ let path:UIBezierPath//具體線段 let color:UIColor//線段對應顏色 init(path:UIBezierPath,color:UIColor){ self.path = path self.color = color }}class PathCreator{ //所有筆畫 private var paths:[NSMutableArray]? //筆畫內當前子線段 private var currentBezierPathInfo:BezierInfo? //當前筆畫的所有子線段 private var currentPath:NSMutableArray? //當前筆畫已經采集處理了幾個觸摸點 private var pointCountInOnePath = 0 static let colors = [UIColor.red,UIColor.orange,UIColor.yellow,UIColor.green,UIColor.blue,UIColor.gray,UIColor.purple] init() { paths = [] } //添加新筆畫 func addNewPath(to:CGPoint,isEraser:Bool)->Void{ //創建起始線段 let path = UIBezierPath() path.lineWidth = 5 path.move(to: to) path.lineJoinStyle = CGLineJoin.round path.lineCapStyle = CGLineCap.round if !isEraser { //綁定線段與顏色信息 currentBezierPathInfo = BezierInfo(path: path, color: PathCreator.colors[0]) }else{ //處于擦除模式,顏色與畫板背景色相同 currentBezierPathInfo = BezierInfo(path: path, color: UIColor.black) } //新建一個筆畫 currentPath = NSMutableArray.init() //將起始線段加入當前筆畫 currentPath!.add(currentBezierPathInfo) pointCountInOnePath = 0 //將當前筆畫加入筆畫數組 paths!.append(currentPath!) } //添加新的點,更新當前筆畫路徑 func addLineForCurrentPath(to:CGPoint,isEraser:Bool) -> Void { pointCountInOnePath += 1//同一筆畫內,每7個點換一次顏色 if pointCountInOnePath % 7 == 0{//換顏色 if let currentBezierPathInfo = currentBezierPathInfo{ //將當前點加入當前子線段,更新當前子線段路徑 currentBezierPathInfo.path.addLine(to: to) } //生成新的子線段 let path = UIBezierPath() path.lineWidth = 5 path.move(to: to) path.lineJoinStyle = CGLineJoin.round path.lineCapStyle = CGLineCap.round if !isEraser{ //給當前子線段設置下一個顏色 currentBezierPathInfo = BezierInfo(path: path, color: PathCreator.colors[currentPath!.count % 7]) }else{ //處于擦除模式,顏色與畫板背景色相同 currentBezierPathInfo = BezierInfo(path: path, color: UIColor.black) } //將當前子線段加入當前筆畫 currentPath!.add(currentBezierPathInfo) }else{ if let currentBezierPathInfo = currentBezierPathInfo{ //將當前點加入當前子線段,更新當前子線段路徑 currentBezierPathInfo.path.addLine(to: to) } } } func drawPaths()->Void{ //畫線 let pathCount = paths!.count for i in 0..<pathCount{ //取出所有筆畫 let onePath = paths![i] let onePathCount = onePath.count for j in 0..<onePathCount{ //繪制每條筆畫內每個子線段 let pathInfo = onePath.object(at: j) as! BezierInfo pathInfo.color.set() pathInfo.path.stroke() } } } func revoke()->Void{ //移走上一筆畫 if paths!.count > 0 { paths!.removeLast() } } func clean()->Void{ //移走所有筆畫 paths!.removeAll() }}知識點總結:
1.結構體是值傳遞
一個基礎概念,但開始使用時還是給忘了。數組[]在swift中是結構體(struct)實現,值傳遞。最開始把currentPath聲明為了[],添加到paths[]中后,后續再去往currentPath中添加元素,paths中的對應的currentpath對象內容并未隨之發生改變,后將currentPath改為了NSMutableArray(引用傳遞).2.selector、@objc、private
(純)swift與oc采用了不同的運行機制,swift不再采用與oc一樣的運行時(runtime)與消息分發機制,selector作為oc運行機制的產物,swift中也對其進行了保留與支持。
@objc修飾符的作用是將swift定義的類、方法等暴露給oc。
于是,下列selector中指定的方法,都要使用@objc進行修飾
cleanBut.addTarget(self, action: #selector(cleanButClick), for: UIControlEvents.touchUpInside)let ges = UIPanGestureRecognizer(target: self, action:#selector(handleGes(ges:)))如果一個swift類繼承自NSObject,swift會默認給該類的非private屬性或方法加上@objc修飾。因為Canvas類(->UIView->UIResponder->NSObject)繼承自NSObject,所以其屬性或方法(非private)都會被自動加上@objc修飾但是因為我代碼中的這幾個selector指向的方法都聲明為了private,所以還是需要手動去做@objc修飾(如果是非private的,可以不寫@objc)@objc private func handleGes(ges:UIPanGestureRecognizer) -> Void3.required的構造函數
required用于修飾構造方法,用于要求子類必需實現對應的構造方法如果子類中沒有實現任何構造方法,則不必去顯式的實現父類要求的required構造方法;而當子類中有定義實現構造方法時,則必需顯式的去實現父類要求的required構造方法,同時還要保留required修飾.當實現一個類Canvas繼承自UIView時,我們可以看到編譯器強制要求我們實現構造方法public init?(coder aDecoder: NSCoder)通過xcode找到該方法是在NSCoding協議中被定義的public protocol NSCoding { public func encode(with aCoder: NSCoder) public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER}可以看到,此處并沒有進行requird修飾,為什么還要求強制實現該構造方法呢?因為在協議中規定的構造方法,不用顯式進行requird修飾,實現協議的對應類默認必需要去實現協議中規定的構造方法,且加上requird修飾4.as
let x:UInt16 = 100let y:UInt8 = 10//x + y會報錯,不自動類型轉換,更安全let n = UInt8(x) + y上面例子中,當我們進行值類型之間的類型轉換(UInt16->UInt8)時,其實借助的是UInt8的構造方法/// Create an instance initialized to `value`. public init(integerLiteral value: UInt8)而當引用類型之間需要進行強制轉換時,則需要借助as操作符因為轉換可能失敗(兩個不相關的類之間進行轉換),所以需要使用as?,轉換結果為一個可選型,不成功時,可選型值為nil當然,如果可以肯定轉換是成功的,則可以使用as!進行轉換,結果為目標類型的對象。另外,看下面這個例子var people:People?let man:Man = Man()people = manprint(people)//可選型變量let beMan = people as! Manprint (beMan)//強制轉化后beMan不是可選型var people:People?let man:Man = Man()people = manprint(people)//可選型變量let beMan = people as! Man?print (beMan)//強制轉化后beMan為可選型轉換后的結果類型完全由as!后面的目標類型決定,即便原對象在轉換之前是可選型對象,但如果轉換的目標類型不是可選型,則轉換后得到的也就不是一個可選型了
新聞熱點
疑難解答