Natasha The Robot, Natasha The Robot, Natasha The Robot Proofreading: saitjr; Finalized: CMB
A widely circulated tweet talks about initializing Swift constants with positional references:
The original code is shown in this gist:
import UIKit import XCPlayground class ViewController: UIViewController { func action() { print("Bing!" ) } override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .whiteColor() let mySwitch : UISwitch = { view.addSubview($0) CenterViewInSuperview($0, horizontal: true, vertical: true) $0.addTarget(self, action: "action", forControlEvents: .TouchUpInside) return $0 }(UISwitch()) let _ : UILabel = { view.addSubview($0) CenterViewInSuperview($0, horizontal: true, vertical: false) $0.text = "Toggle me" $0.font = UIFont.boldSystemFontOfSize(36) ConstrainViews("V:[view1]-30-[view2]", views: $0, mySwitch) return $0 }(UILabel()) } } ViewController() XCPlaygroundPage.currentPage.liveView = ViewController() XCPlaygroundPage.currentPage.needsIndefiniteExecution = trueCopy the code
Since the tweet was too brief and didn’t work as I expected, I was confused. So I want to write a more detailed article on this issue here.
The problem
Declaring a constant and then initializing it in an immediate closure, rather than setting it later in viewDidLoad or something similar, is a very common (and a good one!) way to write it in Swift. .
Let purpleView: UIView = {// Initialize the view here // Call it "view". let view = UIView() view.backgroundColor = .purpleColor() return view }()Copy the code
I always feel awkward naming an extra UIView in a closure. There are two UIView instances in this code: purpleView and View. Should the view be called purpleView? I haven’t found a good solution to this naming problem yet.
The solution
When I was excited to see tweets with $0 instead of tedious naming variables, I immediately tried it out:
Let yellowView: UIView = {$0. BackgroundColor =.yellowcolor () return $0}()Copy the code
But it doesn’t work… Looking more closely at the @ericasadun code, I realize that I must pass in an initialized UIView instance when the closure executes:
Let yellowView: UIView = {$0.backgroundcolor =.yellowcolor () return $0 // Make sure to pass UIView()}(UIView() in parentheses on the next line.Copy the code
Of course it works this time!
conclusion
I really like the $0 notation here, rather than explicitly renaming a variable. Why didn’t I think of that? I’m so angry. Now, however, I know that I have to pass in an initialized UIView(), which does logically make sense. I’ll probably write it this way for the rest of my life. This is an elegant way to write $0, so why not!
Join our Swift Community Celebration on September 1st and 2nd in Nyc at 🎉 and save $100 with this promo code NATASHATHEROBOT!
update
@khanlou points out that there is a library called Then that is better for writing more readable code:
let label = UILabel().then {
$0.textAlignment = .Center
$0.textColor = .blackColor()
$0.text = "Hello, World!"
}Copy the code
But I wasn’t too happy importing the library into my project, @khanlou pointed out again that the library only had 15 lines of code, so my final idea was to copy the 15 lines into my project! Since there is only import Foundation
public protocol Then {} extension Then where Self: Any { /// Makes it available to set properties with closures just after initializing. /// /// let label = UILabel().then { /// $0.textAlignment = .Center /// $0.textColor = UIColor.blackColor() /// $0.text = "Hello, World!" /// } public func then(@noescape block: Inout Self -> Void) -> Self {var copy = Self block(©) return copy}} extension Then where Self: AnyObject { /// Makes it available to set properties with closures just after initializing. /// /// let label = UILabel().then { /// $0.textAlignment = .Center /// $0.textColor = UIColor.blackColor() /// $0.text = "Hello, World!" /// } public func then(@noescape block: Self -> Void) -> Self { block(self) return self } } extension NSObject: Then {}Copy the code
You can also check out @ericasadun for a great solution! (For this gist, the code moves below)
// @discardableResult to be added
// @noescape needs to move to type annotation
// needs to add _ for item
public func with(item: T, @noescape update: (inout T) throws -> Void) rethrows -> T {
var this = item; try update(&this); return this
}Copy the code
This article is translated by SwiftGG translation team and has been authorized to be translated by the authors. Please visit swift.gg for the latest articles.