├── .gitignore ├── LICENSE ├── README.md ├── Swift2-ProtocolExtensions-ErrorRendering.playground ├── Contents.swift ├── contents.xcplayground ├── playground.xcworkspace │ └── contents.xcworkspacedata └── timeline.xctimeline ├── example.gif └── example.mov /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | #Pods/ 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jeff Hurray 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift 2.0 Protocol Extension Example 2 | Playground showing how to use Swift2 protocol extensions to render errors in UIViews and UIViewControllers without subclassing or creating classes 3 | 4 | 5 | 6 | **UIViewControllers** will render an error as a toast banner 7 | 8 | **UIViews** will render an error by adding a generic error label to the views top right corner. 9 | 10 | This was inspired by a [sweet article](http://krakendev.io/blog/subclassing-can-suck-and-heres-why) by [KrakenDev](http://krakendev.io) 11 | 12 | 13 | 14 | ```swift 15 | 16 | import UIKit 17 | import XCPlayground 18 | 19 | struct ErrorOptions { 20 | let message: String 21 | let tintColor: UIColor 22 | 23 | init(message: String = "Error!", tintColor: UIColor = UIColor.clearColor()) { 24 | self.message = message 25 | self.tintColor = tintColor 26 | } 27 | } 28 | 29 | typealias ErrorRenderingCompletionBlock = ()->() 30 | 31 | protocol ErrorPopoverRenderer { 32 | func presentError(errorOptions: ErrorOptions) 33 | func presentError(errorOptions: ErrorOptions, completion : ErrorRenderingCompletionBlock?) 34 | } 35 | 36 | extension ErrorPopoverRenderer { 37 | func presentError(errorOptions: ErrorOptions = ErrorOptions()) { 38 | self.presentError(errorOptions, completion: nil) 39 | } 40 | } 41 | 42 | extension ErrorPopoverRenderer where Self: UIViewController { 43 | 44 | func presentError(errorOptions: ErrorOptions = ErrorOptions(), completion : ErrorRenderingCompletionBlock?) { 45 | let errorBanner = UILabel() 46 | errorBanner.backgroundColor = errorOptions.tintColor 47 | errorBanner.textAlignment = .Center 48 | errorBanner.adjustsFontSizeToFitWidth = true 49 | errorBanner.font = UIFont.systemFontOfSize(20.0) 50 | errorBanner.textColor = UIColor.whiteColor() 51 | errorBanner.text = errorOptions.message 52 | let height : CGFloat = 50 53 | errorBanner.frame = CGRect(x: 0, y: -height, width: CGRectGetWidth(self.view.bounds), height: height) 54 | self.view.addSubview(errorBanner) 55 | UIView.animateWithDuration(0.8, animations: { () -> Void in 56 | errorBanner.transform = CGAffineTransformMakeTranslation(0, height) 57 | }) { (done1) -> Void in 58 | UIView.animateWithDuration(0.8, delay: 0.5, options: UIViewAnimationOptions.CurveEaseOut, animations: { () -> Void in 59 | errorBanner.transform = CGAffineTransformIdentity 60 | }, completion: { (done2) -> Void in 61 | errorBanner.removeFromSuperview() 62 | if let completionBlock = completion { 63 | completionBlock() 64 | } 65 | }) 66 | } 67 | } 68 | } 69 | 70 | extension ErrorPopoverRenderer where Self: UIView { 71 | 72 | func presentError(errorOptions: ErrorOptions = ErrorOptions(), completion : ErrorRenderingCompletionBlock?) { 73 | let errorBanner = UILabel() 74 | errorBanner.backgroundColor = errorOptions.tintColor 75 | errorBanner.textAlignment = .Center 76 | errorBanner.adjustsFontSizeToFitWidth = true 77 | errorBanner.font = UIFont.systemFontOfSize(18.0) 78 | errorBanner.text = "!" 79 | errorBanner.textColor = UIColor.redColor() 80 | let size : CGFloat = 32.0 81 | let padding : CGFloat = 8.0 82 | errorBanner.layer.cornerRadius = size/2.0 83 | errorBanner.layer.borderColor = UIColor.redColor().CGColor 84 | errorBanner.layer.borderWidth = 1.0 85 | errorBanner.frame = CGRect(x: CGRectGetWidth(self.bounds) - size - padding, y: padding, width: size, height: size) 86 | self.addSubview(errorBanner) 87 | if let completionBlock = completion { 88 | completionBlock() 89 | } 90 | } 91 | } 92 | 93 | extension UIViewController : ErrorPopoverRenderer {} 94 | extension UIView : ErrorPopoverRenderer {} 95 | 96 | 97 | let viewController = UIViewController() 98 | viewController.view.frame = CGRect(x: 0, y: 0, width: 375, height: 667) 99 | viewController.view.backgroundColor = UIColor.whiteColor() 100 | XCPShowView("Controller View", view: viewController.view) 101 | 102 | let errorOptions = ErrorOptions(message: "OMG an error!", tintColor: UIColor.redColor()) 103 | viewController.presentError(errorOptions) { () -> () in 104 | viewController.view.presentError() 105 | } 106 | 107 | 108 | 109 | ``` -------------------------------------------------------------------------------- /Swift2-ProtocolExtensions-ErrorRendering.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | //: Inspired by http://krakendev.io/blog/subclassing-can-suck-and-heres-why 3 | 4 | import UIKit 5 | import XCPlayground 6 | 7 | struct ErrorOptions { 8 | let message: String 9 | let tintColor: UIColor 10 | 11 | init(message: String = "Error!", tintColor: UIColor = UIColor.clearColor()) { 12 | self.message = message 13 | self.tintColor = tintColor 14 | } 15 | } 16 | 17 | typealias ErrorRenderingCompletionBlock = ()->() 18 | 19 | protocol ErrorPopoverRenderer { 20 | func presentError(errorOptions: ErrorOptions) 21 | func presentError(errorOptions: ErrorOptions, completion : ErrorRenderingCompletionBlock?) 22 | } 23 | 24 | extension ErrorPopoverRenderer { 25 | func presentError(errorOptions: ErrorOptions = ErrorOptions()) { 26 | self.presentError(errorOptions, completion: nil) 27 | } 28 | } 29 | 30 | 31 | 32 | extension ErrorPopoverRenderer where Self: UIViewController { 33 | 34 | func presentError(errorOptions: ErrorOptions = ErrorOptions(), completion : ErrorRenderingCompletionBlock?) { 35 | let errorBanner = UILabel() 36 | errorBanner.backgroundColor = errorOptions.tintColor 37 | errorBanner.textAlignment = .Center 38 | errorBanner.adjustsFontSizeToFitWidth = true 39 | errorBanner.font = UIFont.systemFontOfSize(20.0) 40 | errorBanner.textColor = UIColor.whiteColor() 41 | errorBanner.text = errorOptions.message 42 | let height : CGFloat = 50 43 | errorBanner.frame = CGRect(x: 0, y: -height, width: CGRectGetWidth(self.view.bounds), height: height) 44 | self.view.addSubview(errorBanner) 45 | UIView.animateWithDuration(0.8, animations: { () -> Void in 46 | errorBanner.transform = CGAffineTransformMakeTranslation(0, height) 47 | }) { (done1) -> Void in 48 | UIView.animateWithDuration(0.8, delay: 0.5, options: UIViewAnimationOptions.CurveEaseOut, animations: { () -> Void in 49 | errorBanner.transform = CGAffineTransformIdentity 50 | }, completion: { (done2) -> Void in 51 | errorBanner.removeFromSuperview() 52 | if let completionBlock = completion { 53 | completionBlock() 54 | } 55 | }) 56 | } 57 | } 58 | } 59 | 60 | extension ErrorPopoverRenderer where Self: UIView { 61 | 62 | func presentError(errorOptions: ErrorOptions = ErrorOptions(), completion : ErrorRenderingCompletionBlock?) { 63 | let errorBanner = UILabel() 64 | errorBanner.backgroundColor = errorOptions.tintColor 65 | errorBanner.textAlignment = .Center 66 | errorBanner.adjustsFontSizeToFitWidth = true 67 | errorBanner.font = UIFont.systemFontOfSize(18.0) 68 | errorBanner.text = "!" 69 | errorBanner.textColor = UIColor.redColor() 70 | let size : CGFloat = 32.0 71 | let padding : CGFloat = 8.0 72 | errorBanner.layer.cornerRadius = size/2.0 73 | errorBanner.layer.borderColor = UIColor.redColor().CGColor 74 | errorBanner.layer.borderWidth = 1.0 75 | errorBanner.frame = CGRect(x: CGRectGetWidth(self.bounds) - size - padding, y: padding, width: size, height: size) 76 | self.addSubview(errorBanner) 77 | if let completionBlock = completion { 78 | completionBlock() 79 | } 80 | } 81 | } 82 | 83 | extension UIViewController : ErrorPopoverRenderer {} 84 | extension UIView : ErrorPopoverRenderer {} 85 | 86 | 87 | let viewController = UIViewController() 88 | viewController.view.frame = CGRect(x: 0, y: 0, width: 375, height: 667) 89 | viewController.view.backgroundColor = UIColor.whiteColor() 90 | XCPlaygroundPage.currentPage.liveView = viewController 91 | let errorOptions = ErrorOptions(message: "OMG an error!", tintColor: UIColor.redColor()) 92 | viewController.presentError(errorOptions) { () -> () in 93 | viewController.view.presentError() 94 | } 95 | 96 | -------------------------------------------------------------------------------- /Swift2-ProtocolExtensions-ErrorRendering.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Swift2-ProtocolExtensions-ErrorRendering.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Swift2-ProtocolExtensions-ErrorRendering.playground/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhurray/Swift2-Protocol-Extension-Example/baea4953c1d32513af04438870c0c366279a4474/example.gif -------------------------------------------------------------------------------- /example.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhurray/Swift2-Protocol-Extension-Example/baea4953c1d32513af04438870c0c366279a4474/example.mov --------------------------------------------------------------------------------