Slide 1

Slide 1 text

Optimizing Swift code for separation of concerns and simplicity 樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄SwiftπЄϖ΄๋晒۸ "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 1

Slide 2

Slide 2 text

Intro - Pebble - Twitter (Fabric) - "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 2

Slide 3

Slide 3 text

Coding Principles πЄϔΰЀν΄ܻ㳷 "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 3

Slide 4

Slide 4 text

Coding Principles πЄϔΰЀν΄ܻ㳷 - Simplicity / 㶨奈 - Conciseness / 墋侜 - Clarity / ก嘦 "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 4

Slide 5

Slide 5 text

Separation of Concerns 樛ஞ΄ړ櫝 - Code is read much more often than it is written πЄϖ΅䨗͡΢Ρͩ;ΞΠΘ抎Δ΢Ρͩ;΄ො͢ग़͚ - Separating the "what" from the "how" How ͡Ο what ΨڔΠ櫝ͯ "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 5

Slide 6

Slide 6 text

Example 1 (Before) func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { sendButton.isEnabled = textView.text.utf16.count + text.utf16.count - range.length <= 140 return true } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 6

Slide 7

Slide 7 text

Example 1 (After) private extension String { var characterCountUsingBackendPolicy: Int { return utf16.count } } func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { let characters = textView.text.characterCountUsingBackendPolicy + text.characterCountUsingBackendPolicy - range.length let characterLimit = 140 sendButton.isEnabled = characters <= characterLimit return true } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 7

Slide 8

Slide 8 text

Example 2 (Before) api.requestReplies(postID: 4815162342) { [weak self] result in switch result { case .success(let replies): var filteredReplies: [Reply] = [] for reply in replies { if !user.isBlocking(reply.author) { filteredReplies.append(reply) } } self?.replies = filteredReplies case .failure: // ... } } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 8

Slide 9

Slide 9 text

Example 2 (After) extension Collection where Element == Reply { var filteringBlockedContent: [Reply] { return filter { !user.isBlocking($0.author) } } } api.requestReplies(postID: 4815162342) { [weak self] result in switch result { case .success(let replies): self?.replies = replies.filteringBlockedContent case .failure: // ... } } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 9

Slide 10

Slide 10 text

Example 3 (Before) NSLayoutConstraint.activate([ subview.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: insets.left), subview.topAnchor.constraint(equalTo: view.topAnchor, constant: insets.top), view.trailingAnchor.constraint(equalTo: subview.trailingAnchor, constant: insets.right), view.bottomAnchor.constraint(equalTo: subview.bottomAnchor, constant: insets.bottom) ]) "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 10

Slide 11

Slide 11 text

Example 3 (After) NSLayoutConstraint.activate([ subview.leadingAnchor ≈ view.leadingAnchor + insets.left, subview.topAnchor ≈ view.topAnchor + insets.top, view.trailingAnchor ≈ subview.trailingAnchor + insets.right, view.bottomAnchor ≈ subview.bottomAnchor + insets.bottom ]) infix operator ≈ : LayoutAnchorPrecedence func ≈ (lhs: NSLayoutAnchor, rhs: LayoutAnchorTransform) -> NSLayoutConstraint { return lhs.constraint(equalTo: rhs.anchor, constant: rhs.constant) } func + (lhs: NSLayoutAnchor, rhs: CGFloat) -> LayoutAnchorTransform { return LayoutAnchorTransform(anchor: lhs, constant: rhs) } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 11

Slide 12

Slide 12 text

Example 3 (After 2) NSLayoutConstraint.activate(NSLayoutConstraint.anchoring(subview, within: view)) extension NSLayoutConstraint { static func anchoring(_ subview: UIView, within view: UIView, insets: UIEdgeInsets = .zero) -> [NSLayoutConstraint] { return [ subview.leadingAnchor ≈ view.leadingAnchor + insets.left, subview.topAnchor ≈ view.topAnchor + insets.top, view.trailingAnchor ≈ subview.trailingAnchor + insets.right, view.bottomAnchor ≈ subview.bottomAnchor + insets.bottom ] } } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 12

Slide 13

Slide 13 text

Example 3 (Before and After) // Before NSLayoutConstraint.activate([ subview.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: insets.left), subview.topAnchor.constraint(equalTo: view.topAnchor, constant: insets.top), view.trailingAnchor.constraint(equalTo: subview.trailingAnchor, constant: insets.right), view.bottomAnchor.constraint(equalTo: subview.bottomAnchor, constant: insets.bottom) ]) // After NSLayoutConstraint.activate(NSLayoutConstraint.anchoring(subview, within: view)) "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 13

Slide 14

Slide 14 text

Example 4 class MyView: UIView { var activeConstraints: [NSLayoutConstraint] = [] { willSet { NSLayoutConstraint.deactivate(activeConstraints) } didSet { NSLayoutConstraint.activate(activeConstraints) } } var headerVisible: Bool { didSet { activeConstraints = [ view.topAnchor ≈ (headerVisible ? header.topAnchor : header.bottomAnchor), // more ... ] } } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 14

Slide 15

Slide 15 text

Example 5 (Before) func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 3 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath) if indexPath.row == 0 { cell.textLabel.text = "General" } else if indexPath.row == 1 { cell.textLabel.text = "Notifications" } else if indexPath.row == 2 { cell.textLabel.text = "Log Out" } return cell } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 15

Slide 16

Slide 16 text

Example 5 (After) enum Row { case general, notifications, logout var title: String { switch self { case .general: return "General" // ... } } } let rows: [Row] = [.general, .notifications, .logout] func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return rows.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = collectionView.dequeueReusableCell(... cell.textLabel.text = rows[indexPath.row].title return cell } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 16

Slide 17

Slide 17 text

Example 6 (Before) func showTutorial() { guard !UserDefaults.standard.bool(forKey: "has_seen_tutorial") else { return } // Show tutorial UserDefaults.standard.set(true, forKey: "has_seen_tutorial") } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 17

Slide 18

Slide 18 text

Example 6 (After) var hasSeenTutorial: Bool { get { return UserDefaults.standard.bool(forKey: "has_seen_tutorial") } set { UserDefaults.standard.set(newValue, forKey: "has_seen_tutorial") } } func showTutorial() { guard !hasSeenTutorial else { return } // Show tutorial hasSeenTutorial = true } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 18

Slide 19

Slide 19 text

Example 7 (Before) if #available(iOS 11.0, *) { constraints = [ subview.topAnchor ≈ view.safeAreaLayoutGuide.topAnchor, subview.leadingAnchor ≈ view.safeAreaLayoutGuide.leadingAnchor, subview.bottomAnchor ≈ view.safeAreaLayoutGuide.bottomAnchor, subview.trailingAnchor ≈ view.safeAreaLayoutGuide.trailingAnchor, ] } else { constraints = [ subview.topAnchor ≈ view.topAnchor, subview.leadingAnchor ≈ view.leadingAnchor, subview.bottomAnchor ≈ view.bottomAnchor, subview.trailingAnchor ≈ view.trailingAnchor, ] } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 19

Slide 20

Slide 20 text

Example 7 (After) extension UIView { var tw_safeAreaLayoutGuide: UILayoutGuide { if #available(iOS 11.0, *) { return safeAreaLayoutGuide } if let tw_safeAreaLayoutGuide = objc_getAssociatedObject(self, associatedKey) as? UILayoutGuide { return tw_safeAreaLayoutGuide } let tw_safeAreaLayoutGuide = UILayoutGuide() addLayoutGuide(tw_safeAreaLayoutGuide) NSLayoutConstraint.activate(NSLayoutConstraint.anchoring(tw_safeAreaLayoutGuide, within: self)) objc_setAssociatedObject(self, associatedKey, tw_safeAreaLayoutGuide, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return tw_safeAreaLayoutGuide } } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 20

Slide 21

Slide 21 text

Example 8 (Before) // Set size big enough to make it easy to tap button.frame = CGRect(origin: .zero, size: CGSize(width: 44, height: 44)) "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 21

Slide 22

Slide 22 text

Example 8 (After) class TWMinimumHitAreaButton: UIButton { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { guard !isHidden && enabled && isUserInteractionEnabled && alpha >= 0.01 else { return nil } let lengthOfTappableAreaOutside: CGFloat = 10.0 let minimumHitAreaSize = CGSize(width: 44, height: 44) // Increase the hit frame to be at least as big as `minimumHitArea` let buttonSize = bounds.size let widthToAdd = max(minimumHitAreaSize.width - buttonSize.width, lengthOfTappableAreaOutside * 2) let heightToAdd = max(minimumHitAreaSize.height - buttonSize.height, lengthOfTappableAreaOutside * 2) let largerFrame = bounds.insetBy(dx: -widthToAdd / 2, dy: -heightToAdd / 2) // Perform hit test on larger frame let hit = largerFrame.contains(point) return hit ? self : nil } } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 22

Slide 23

Slide 23 text

Example 9 (Before) /// [ Send ] / [ Close ] / [ Send ] - [ Close ] private let sendButton: UIButton var showSendButton: Bool { didSet { if showSendButton { addSubview(sendButton) } else { sendButton.removeFromSuperview() } configureConstraints() } } private let closeButton: UIButton var showCloseButton: Bool { didSet { // ... } } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 23

Slide 24

Slide 24 text

Example 9 (After) private let sendButton: UIButton private let closeButton: UIButton private let stackView = UIStackView(arrangedViews: sendButton, closeButton) var showSendButton: Bool { didSet { sendButton.isHidden = !showSendButton } } var showCloseButton: Bool { didSet { closeButton.isHidden = !showCloseButton } } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 24

Slide 25

Slide 25 text

Example 10 "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 25

Slide 26

Slide 26 text

Example 10 — ProfileViewController — user details — children views "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 26

Slide 27

Slide 27 text

Example 10 — ProfileViewController — userID — (user details) — (children views) "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 27

Slide 28

Slide 28 text

Example 10 final class ProfileViewController: UIViewController { // Before: private let userInfo: UserInfo private let headerView: ProfileHeaderView // After: private var userInfo: UserInfo? private var headerView: ProfileHeaderView? private var spinner: UIActivityIndicatorView? private var retryButton: UIButton? } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 28

Slide 29

Slide 29 text

Example 10 final class ProfileViewController: UIViewController { private enum State { case pending case loading(spinner: UIActivityIndicatorView) case failed(retryButton: UIButton) case loaded(userInfo: UserInfo, headerView: ProfileHeaderView) } private var state: State = .pending } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 29

Slide 30

Slide 30 text

Example 10 final class ProfileViewController: UIViewController { var state: State = .pending private func loadUserDetails() { state = .loading(spinner: UIActivityIndicatorView()) api.requestUserDetails(userID) { [weak self] result in switch result { case let .success(userInfo): self?.state = .loaded(userInfo: userInfo, headerView: createHeaderView(userInfo)) case let .failure: self?.state = .failed(createRetryButton()) } } } } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 30

Slide 31

Slide 31 text

Example 10 final class ProfileViewController: UIViewController { var visibleView: UIView? { willSet { visibleView?.removeFromSuperview() } didSet { if let visibleView = visibleView { view.addSubview(visibleView) } } } var state: State = .pending { didSet { switch state { case .pending: visibleView = nil case .loading(let spinner): visibleView = spinner case .loaded(_, let profileHeaderView): visibleView = profileHeaderView case .failed(let retryButton): visibleView = retryButton } } } } "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 31

Slide 32

Slide 32 text

Summary - Value in optimizing readability in local scopes ϺЄθϸφπЄϤ΄ݢ抎௔΄๋晒۸΁΅㭅㮔͘͢Ρ - DRY (Don't Repeat Yourself) - Swift enums are awesome "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 32

Slide 33

Slide 33 text

Happy Swifting! Thank you. "Optimizing Swi! code for separation of concerns and simplicity." "樛ஞ΄ړ櫝;㶨奈۸΄͵Η΄Swi!πЄϖ΄๋晒۸" - @Javi 33