Showing posts with label Swift. Show all posts
Showing posts with label Swift. Show all posts

Thursday, October 18, 2018

UIKit protocol of the month: StoryboardInstantiatableViewController

Github Gist.

I want to share this little invention with the community. I use it for the last month and find it notably helpful: protocol StoryboardInstantiatableViewController { static var storyboardId: String { get } } extension StoryboardInstantiatableViewController where Self: UIViewController { static func instantiate(from storyboard: UIStoryboard?) -> Self? { return storyboard?.instantiateViewController(withIdentifier: Self.storyboardId) as? Self } }
This protocol along with the extension provides some syntactic sugar and solves two common problems:
  • Shortens the instantiation
  • Answers, where to store the storyboard id

Usage example:

// Declaration: class TaskViewController: UIViewController, StoryboardInstantiatableViewController { // The same id as specified in Identity inspector static let storyboardId: String = "task" // ... } // Initializing from Storyboard: // ... guard let taskVC = TaskViewController.instantiate(from: storyboard) else { return } navigationController?.pushViewController(taskVC, animated: true)

Update:

Surprisingly, I bumped into another similar protocol, named StoryboardInstantiatable. The main difference that it's designed for use with NSViewController and Cocoa instead of Cocoa Touch. The fact is, that the gist was posted 5 months earlier than this blog post. So Satori Maru is the first one who invented the idea, although I came to it independently.

Tuesday, May 22, 2018

Swift guidelines to follow

I started using Swift in my projects in 2015 and, as any Swift newbie, I was surprised by the lack of code style guidelines. Here I will share my experience of 3 years and point guidelines that worth following in different aspects of development.

Apple Swift API Design Guidelines provide great rules for naming. I also recommend to use Objective-C style prefixes for public extensions to avoid possible conflicts: /// MARK: Module XXX extension UIImageView { func xxx_loadImageFromURL(_ url: URL) { // ... } } // ... customImageView.xxx_loadImageFromURL(url)
Built-in Xcode formatter (^I) closes most questions with indentation. Just accept, that sometimes it can produce strange results, like this:
URLSession.shared.dataTask(with: apiURL) { (data, _, err) in }.resume() instead of this:
URLSession.shared.dataTask(with: apiURL) { (data, _, err) in }.resume()
Considering newlines, I recommend to follow good old Google Objective-C Style Guide, which is stating: "Use vertical whitespace sparingly."

All the rest is covered by The Official raywenderlich.com Guide. This guide is consistent and detailed, yet simple. It's widely adopted in different projects and tutorials, so it's well tested and recognizable. Note, that the guide is optimized for tutorials and sample code, which is good, because it's easy to read. But you can ignore some rules, like Minimal Imports or Use Type Inferred Context, because they don't improve reading but can slow down the writer.

While writing this post I found another style guide from LinkedIn. I don't recommend to follow it. Some rules listed there are obvious, some duplicate Xcode warnings, and some rules are disputable. The guide is way too strict, too large to remember, and thus it will be hard to adopt in your team.

Tuesday, February 20, 2018

Power of Extensions

Article is valid for Swift 4.0.

In this article I want to share my practical and emotional experience of using Swift extensions. I’m really impressed how powerful they become in comparison with old Objective-C categories.

To create a category in Objective-C you must create 2 files (.h and .m) and support both private and public interfaces. Categories are unsafe: compiler allows you to redefine any method in a class/subclass/superclass and according to Docs (Avoid Category Method Name Clashes paragraph) the behavior is undefined. Actually, it depends on the order of files in Compile Sources list. Also, the one moment is not so obvious, but very important: each Objective-C category should have a name. Why that's an issue? – you may ask. – Because the name usually corresponds to the category features so for each new feature you need a new category, that means 2 files. Such operations eat time, thats why.

Now compare it to Swift. Compiled extensions in swift are much safer and predictable. Try to redefine a method or variable – you will get an `Invalid redeclaration` compiler error. Swift extension doesn't need a name and to create it you usually don't need a separate file. Thanks to this I'm shooting extensions like from a machine gun.

Interesting, that Objective-C also has its own extensions which are just categories without a name. But such anonymous categories can be defined only in the .m file and therefore are limited.

Here I put some common extension applications:
  1. Code grouping. Implementing protocols in the separate blocks: // MARK: UITableViewDataSource extension ScheduleViewController: UITableViewDataSource { // ... } (Though protocol methods implemented in extensions couldn’t be overridden yet.)

    Thematic or logical scopes:
    // MARK: - PRIVATE private extension AppDelegate { func makeRootViewController() -> UIViewController { // ... } }
  2. New way to define constants:
    extension Int { static let maxLocalNotificationsCount = 64 } extension TimeInterval { static let secondsInMinute: TimeInterval = 60 static let secondsInHour: TimeInterval = 60 * .secondsInMinute }
    Usage: request.timeoutInterval = 2 * .secondsInMinute

  3. Default implementation. A powerful feature since Swift 3. protocol OffsetListParserProtocol: class { // ... func loadNext(completion: LoadHandler?) } extension OffsetListParserProtocol { func loadNext(completion: LoadHandler?) { guard !isFinished && !isLoading else { completion?(nil, nil) return } // Start loading... } }
    BTW, it's the only way to create a common implementation for struct's because they cannot inherit.

  4. And of course extending the abilities of existing system, third party or your own types: public extension UIViewController { func firstAncestorOfType(_ type: T.Type) -> T? where T: UIViewController { guard let someParent = parent else { return nil } if let requiredParent = someParent as? T { return requiredParent } else { return someParent.firstAncestorOfType(type) } } }

Handy extensions that we create from time to time can be aggregated in git submodules or Swift frameworks, allowing to share improvements among a whole bunch of projects, greatly rising the speed of development. Instead of copypasting the code snippets you just create extensions (less often types or global functions). Like this:
// Example from my SwiftEssentials framework public extension UIColor { var hexString: String { var r: CGFloat = 0 var g: CGFloat = 0 var b: CGFloat = 0 getRed(&r, green: &g, blue: &b, alpha: nil) let intR = Int(round(r * 255)) let intG = Int(round(g * 255)) let intB = Int(round(b * 255)) return String(format: "#%02lX%02lX%02lX", intR, intG, intB) } convenience init(Hº: CGFloat, S%: CGFloat, B%: CGFloat, A: CGFloat) { self.init(hue: Hº / 360, saturation: S% / 100, brightness: B% / 100, alpha: A) } convenience init(hex: UInt32, alpha: CGFloat) { self.init( red: CGFloat((hex & 0xFF0000) >> 16) / 255, green: CGFloat((hex & 0x00FF00) >> 8) / 255, blue: CGFloat(hex & 0x0000FF) / 255, alpha: alpha ) } }
I’m very excited about new possibilities that Swift provides. I have learned Swift since the origin, see how it grows and I don't stop to notice how fast, convenient and productive this language is. Due to extension's I don’t bother if I see any imperfections in the system libraries anymore. I just fix them and forget :)
public extension UIView { func removeSubviews() { for subview in subviews { subview.removeFromSuperview() } } }

Saturday, February 3, 2018

Changing view's type on the Storyboard


Suppose the design of your app changed, and now you need to replace the Table View with a Collection View keeping all the constraints along with views hierarchy.
Drop a new View Controller on the storyboard and put a Collection View inside. Rename it in the left panel (called Document Outline). Name it "The Replacement" so you will be able recognize it later. Now switch to the Source Code through the Open As.
Now the storyboard looks like XML markup. Find the Table View you need to replace, it will be scoped within the tableView tag.


Copy its id (example: id="8Oa-xL-ums"). It points to the view in constraints and outlets. Now find the new Collection View under the collectionView tag.


Copy everything including the enclosing tags: <collectionView ...</collectionView> and paste it instead of the tableView. Replace Collection View's id with the value you copied from the Table View. Before switching back to Interface Builder you need to clean up the storyboard. Remove the scene of the temporary View Controller that you created at the beginning of the tutorial. Delete everything between tags and the preceding comment: <!--View Controller><scene ...</scene>. If you don't do this you will have an error because of duplicated id.


Now you will have the new Collection View constrained like the Table View but misplaced, because it's was copied with the rect.
Click Update Frames and enjoy the result!

Here is the list of storyboard tags for different UI classes. Who knows, may be it will be useful.

arscnView ARSCNView
arskView ARSKView
glkView GLKView
mapView MKMapView
mtkView MTKView
sceneKitView SCNView
skView SKView
activityIndicatorView UIActivityIndicatorView
button UIButton
collectionView UICollectionView
datePicker UIDatePicker
imageView UIImageView
label UILabel
navigationBar UINavigationBar
pageControl UIPageControl
pickerView UIPickerView
progressView UIProgressView
scrollView UIScrollView
searchBar UISearchBar
segmentedControl UISegmentedControl
slider UISlider
stackView UIStackView
stepper UIStepper
switch UISwitch
tabBar UITabBar
tableView UITableView
textField UITextField
textView UITextView
toolbar UIToolbar
view UIView
containerView UIView (with embedded View Controller)
visualEffectView UIVisualEffectView
webView UIWebView
wkWebView WKWebView

Saturday, January 27, 2018

Catching nil as Error

Github gist.

I've posted this as an answer on stackoverflow before, and the information was highly appreciated by some people. The question was like: “How to catch any error, specially unexpectedly found nil in Swift?" Though the author was asking about catching system errors, I decided to post my super-handy Unwrap Snippet there.
struct UnwrapError: Error, CustomStringConvertible { let optional: T? public var description: String { return "Found nil while unwrapping \(String(describing: optional))!" } } public func unwrap(_ optional: T?) throws -> T { if let real = optional { return real } else { throw UnwrapError(optional: optional) } }
The idea of this approach is to replace multiple if let and guard let statements with a single do-try-catch block. You can:
do { // Parse JSON and assign variables which were defined somewhere above let dictionary = try unwrap(JSONSerialization.jsonObject( with: data, options: .allowFragments) as? [String: Any]) isAdsEnabled = try unwrap(dictionary["isAdsEnabled"] as? Bool) // While calling function which require a non-optional parameter in one line of code imageView.image = try UIImage(data: unwrap(receivedData)) // And also you can simplify the building of multipart data var data = Data() data += try unwrap(dispositionString.data(using: .utf8)) data += someContent data += try unwrap("\r\n".data(using: .utf8)) } catch error { // Handle error // ... // and exit return nil }
Those who make apps with web backend will definitely like.

Friday, January 19, 2018

Safer parsing with JSONSerialization in Swift

Github gist.

Most people use the following snippet to get a value from an object provided by the JSONSerialization:
guard let name = jsonDictionary["name"] as? String else { return } ...
It's not obvious, but this code is unsafe. It can easily fail if the "name" value will be "111". In this case it may be decoded as NSNumber and casting to String will always fail. The same thing with numbers:
guard let id = jsonDictionary["id"] as? Int else { return } // id can be a "3" String! ... A value like "3" can be decoded both as NSString and NSNumber (more often).

I didn't find any tutorial that would take this issue into account. All of them recommend to use as? to unknown JSON object to basic type. So I post here the correct snippets that will provide stable and predictable results in all possible cases.

JSON helpers

extension String { init?(jsonObject: Any?) { guard jsonObject is NSNull == false else { return nil } let aNSObject = jsonObject as? NSObject if let description = aNSObject?.description { self = description } else { return nil } } } extension Int { var boolValue: Bool { return self != 0 } init?(jsonObject: Any?) { if let number = jsonObject as? NSNumber { self = number.intValue } else if let string = jsonObject as? NSString { self = string.integerValue } else { return nil } } } extension Bool { init?(jsonObject: Any?) { if let integer = Int(jsonObject: jsonObject) { self = integer.boolValue } else { return nil } } } extension Double { init?(jsonObject: Any?) { if let number = jsonObject as? NSNumber { self = number.doubleValue } else if let string = jsonObject as? NSString { self = string.doubleValue } else { return nil } } }
All objects produced by JSONSerialization are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.

How to Record Calls on iPhone