Skip to content

Latest commit

Β 

History

History
1745 lines (1204 loc) Β· 51.8 KB

README.md

File metadata and controls

1745 lines (1204 loc) Β· 51.8 KB

Help

In this project, I have collected various best practices and iOS development tips. This is my personal development assistant.

Index

Clean Code

Raising code readability in iOS development. Thanks to the application of these tips, your code will become readable, which will further ensure convenience and speed of working with it.

The code has a clear structure, due to which the logic is more obvious, the code is easy to read, you can quickly find what you are looking for, and besides, it is just pleasant to look at it.

final class CleanViewController: UIViewController {
    
    // MARK: - IBOutlets
    @IBOutlet private weak var searchBar: UISearchBar!
    @IBOutlet private weak var tableView: UITableView!
    
    
    // MARK: - Public Properties
    var userID: String?
    weak var delegate: SomeDelegate?
    
    
    // MARK: - Private Properties
    private let userService = UserService()
    private var userList: [User]?
    
    
    // MARK: - Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupNavigationBar()
    }
    
    
    // MARK: - Private Methods
    private func setupNavigationBar() {
        navigationController?.navigationBar.backgroundColor = .red
        navigationItem.title = "Some"
    }
    
    
    // MARK: - IBActions
    @IBAction private func cancelButtonPressed(_ sender: UIBarButtonItem) {
        dismiss(animated: true, completion: nil)
    }
    
}

We move the logic out of the lifecycle methods into separate methods. The logic inside the methods of the ViewController lifecycle should be moved into separate methods, even if you have to create a method with one line of code. Today one, and tomorrow ten.

❌ NOT Preferred

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationController?.navigationBar.backgroundColor = .red
        someButton.layer.cornerRadius = 10
        someButton.layer.masksToBounds = true
        navigationItem.title = "Some"
        print("Some")
    }


βœ… Preferred

    // MARK: - Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupNavigationBar()
        setupSomeButton()
        printSome()
    }
    
    
    // MARK: - Private Methods
    private func setupNavigationBar() {
        navigationController?.navigationBar.backgroundColor = .red
        navigationItem.title = "Some"
    }
    
    private func setupSomeButton() {
        someButton.layer.cornerRadius = 10
        someButton.layer.masksToBounds = true
    }
    
    private func printSome() {
        print("Some")
    }

Using an extension to implement protocols. Move protocols implementation into extensions with mark // MARK: β€” SomeProtocol

❌ NOT Preferred

final class CleanViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    // all methods
}


βœ… Preferred

final class CleanViewController: UIViewController {

  // class stuff here
  
}


// MARK: - Table View Data Source
extension CleanViewController: UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        return userList?.count ?? 0
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = UITableViewCell()
        return cell
    }
    
}

To improve clarity, you need to highlight logically related elements using an empty string.

❌ NOT Preferred

    private func showActivityIndicator(on viewController: UIViewController) {
        activityIndicator.center = viewController.view.center
        loadingView.backgroundColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
        loadingView.alpha = 0.5
        activityIndicator.hidesWhenStopped = true
        activityIndicator.style = .whiteLarge
        loadingView.center = viewController.view.center
        loadingView.clipsToBounds = true
        loadingView.layer.cornerRadius = 15
        viewController.view.addSubview(loadingView)
        viewController.view.addSubview(activityIndicator)
        activityIndicator.startAnimating()
    }


βœ… Preferred

    private func showActivityIndicator(on viewController: UIViewController) {
        activityIndicator.center = viewController.view.center
        activityIndicator.hidesWhenStopped = true
        activityIndicator.style = .whiteLarge
        
        loadingView.center = viewController.view.center
        loadingView.backgroundColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
        loadingView.alpha = 0.5
        loadingView.clipsToBounds = true
        loadingView.layer.cornerRadius = 15
        
        viewController.view.addSubview(loadingView)
        viewController.view.addSubview(activityIndicator)
        
        activityIndicator.startAnimating()
    }

Do not leave unnecessary comments (default), empty methods or dead functionality - it makes code dirty. Attention to the AppDelegate class, most likely you will find empty methods there with comments inside.

❌ NOT Preferred

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }
//
//    func someFunc() {
//        print("Some")
//    }

    func applicationWillResignActive(_ application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain
        //types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits
        //the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
    }


βœ… Preferred

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
            return true
    }

}

The main MARKs for splitting the code into logically related blocks and their sequence.

// MARK: - IBOutlets

// MARK: - Public Properties

// MARK: - Private Properties

// MARK: - Initializers

// MARK: - Lifecycle

// MARK: - View Life Cycle

// MARK: - Object Life Cycle

// MARK: - Navigation

// MARK: - Public Methods

// MARK: - Private Methods

// MARK: - IBActions

// MARK: - Actions

// MARK: - Application Shortcut Support

// MARK: - Segue preparation

// MARK: - Table view data source

Semantic Commit

  • feat: (new feature for the user, not a new feature for build script)
  • fix: (bug fix for the user, not a fix to a build script)
  • docs: (changes to the documentation)
  • style: (formatting, missing semi colons, etc; no production code change)
  • refactor: (refactoring production code, eg. renaming a variable)
  • test: (adding missing tests, refactoring tests; no production code change)
  • chore: (updating grunt tasks etc; no production code change) chore: release v0.5.3'

Shortcut

  • Build: ⌘ + B
  • Run: ⌘ + R
  • Test: ⌘ + U
  • Stop: ⌘ + .
  • Clean: ⌘ + ⇧ + K
  • Open quickly: ⇧ + ⌘ + O
  • Code completion: βŒƒ + Space
  • Opening a file: ⇧ + ⌘ + O
  • Navigation: ⇧ + ⌘ + J
  • Jump to Definition: βŒƒ + ⌘ + J
  • Multiple Cursors: βŒ₯ + ⌘ + E
  • All Refactor: βŒƒ + ⌘ + E
  • Reorder Statements: ⌘ + βŒ₯ + ( ] or [ )
  • Open Inspectors: βŒ₯ + ⌘ + 0
  • Open Navigation: ⌘ + 0

⬆ Back to Index

Protocols

A type with a customized textual representation. Types that conform to the CustomStringConvertible protocol can provide their own representation to be used when converting an instance to a string. The String(describing:) initializer is the preferred way to convert an instance of any type to a string. If the passed instance conforms to CustomStringConvertible, the String(describing:) initializer and the print(_:) function use the instance’s custom description property. Accessing a type’s description property directly or using CustomStringConvertible as a generic constraint is discouraged.

struct Card: CustomStringConvertible {

    var description: String { return "\(rank) \(suit)"}

    var suit: Suit
    var rank: Rank

    enum Suit: String, CustomStringConvertible {
        var description: String { return self.rawValue }
        case spades = "♠️"
        case hearts = "β™₯️"
        case clubs = "♣️"
        case diamonds = "♦️"
    }

    enum Rank: Int, CustomStringConvertible {
        case jack = 11
        case lady = 12
        case king = 13
        case ace = 14
        var description: String { return "\(self.rawValue)" }
    }
}

let card = Card(suit: .hearts, rank: .king)
print(card) // 13 β™₯️ nice print

Documentation

struct User: Equatable {
    let login: String
    let password: Int
    
    static func == (lhs: User, rhs: User) -> Bool {
        return lhs.password == rhs.password
    }
}

let userOne = User(login: "mike", password: 123 )
let userTwo = User(login: "mikeeeee", password: 123 )

userOne == userTwo ? print("login") : print("not login")
// login

Documentation

struct User: Comparable {
    
    let name: String
    let money: Int
    
    static func < (lhs: User, rhs: User) -> Bool {
        return lhs.money < rhs.money
    }
}

let mike = User(name: "Mike", money: 50 )
let bob = User(name: "Bob", money: 40 )

mike < bob // false
mike > bob // true

⬆ Back to Index

Extensions

An extension for Int that returns a random number works with range of numbers. Negative numbers converts to positive.

  • Example: 17.random will return 0 to 17 (not including 17).
  • Example: -5.random, -5 convert to 5, will return 0 to 5 (not including 5).
extension Int {
    var random: Int {
        if self > 0 {
            return Int.random(in: 0..<self)
        } else if self < 0 {
            return Int.random(in: 0..<abs(self))
        } else {
            return 0
        }
    }
}
// Compare string of numbers
extension String {
    static func ==(lhs: String, rhs: String) -> Bool {
        return lhs.compare(rhs, options: .numeric) == .orderedSame
      }

      static func <(lhs: String, rhs: String) -> Bool {
        return lhs.compare(rhs, options: .numeric) == .orderedAscending
      }

      static func <=(lhs: String, rhs: String) -> Bool {
        return lhs.compare(rhs, options: .numeric) == .orderedAscending || lhs.compare(rhs, options: .numeric) == .orderedSame
      }

      static func >(lhs: String, rhs: String) -> Bool {
        return lhs.compare(rhs, options: .numeric) == .orderedDescending
      }

      static func >=(lhs: String, rhs: String) -> Bool {
        return lhs.compare(rhs, options: .numeric) == .orderedDescending || lhs.compare(rhs, options: .numeric) == .orderedSame
      }
}

⬆ Back to Index

Working Code

  • Error handler, using the throws keyword we indicate that the function can receive an error.
  • Using the throw key we throw an error into the NSError instance.
  • do catch - this error handler helps to catch the error.
  • In this example, we are trying to call the function that we marked with the keyword throws.
  • Before calling the function, put the try keyword.
  • If the function gets an error, we catch it catch.
func divide(_ a: Int, by b: Int) throws -> Double {
    guard b != 0 else { throw NSError(domain: "The number 'b' must not be zero", code: 100) }
    return Double(a / b)
}

/// Checks in 'do catch'
do {
    try divide(10, by: 0)
} catch let error {
    error.localizedDescription
}

/// Checks in 'try!' or 'try?'
let myError = try! divide(10, by: 0)
let myError = try? divide(10, by: 0)

/// Example with user data
enum NetworkError: Error {
    case notPage
    case networkError
}

class Network {
    static let responses = [200, 404, 500]
    static func request() -> Int {
        return responses.randomElement() ?? 0
    }
}

class NetworkManager {
    func userRequest(text: String) throws -> String {
        let statusCode = Network.request()
        guard statusCode != 404 else { throw NetworkError.notPage }
        guard statusCode != 500 else { throw NetworkError.networkError }
        return "\(statusCode): image \(text)"
    }
}

class Browser {
    let networkManager = NetworkManager()
    
    func getImage(text: String) {
        do {
            let result = try networkManager.userRequest(text: text)
            print(result)
        } catch {
            switch error {
            case NetworkError.notPage:
                print("Not Page")
            case NetworkError.networkError:
                print("Network Error")
            default:
                print(error.localizedDescription)
            }
        }
    }
}

let safari = Browser()
safari.getImage(text: "page 1")
safari.getImage(text: "page 2")
safari.getImage(text: "page 3")

Type casting is a way to check the type of an instance, or to treat that instance as a different superclass or subclass from somewhere else in its own class hierarchy.

Type casting in Swift is implemented with the is and as operators. These two operators provide a simple and expressive way to check the type of a value or cast a value to a different type.

/// Type Casting
let unknown: Any = "Steve"

if let name = unknown as? String {
    print(name)
} else {
    print("'unknown' is not type String")
}

/// Checking Type
unknown is Int ? print(unknown) : print("'unknown' is not type Int")

/// Downcasting
/// The variable `tom` of the type `Superclass` assigns an instance of `Subclass`. 
/// If we want to access the properties of the subclass `Subclass` then we need to do Downcasting.
/// We cannot directly access the property of the `Subclass`, since the variable `tom` has the type `Superclass`.
class Superclass {
    var name: String = "Tom"
}

class Subclass: Superclass {
    var age: Int = 15
}

let tom: Superclass = Subclass() // The variable type of 'Superclass' assigns an instance of 'Subclass'

tom.age // error
if let value = tom as? Subclass {
    print(value.age)
}

It’s sometimes useful to define a class, structure, or enumeration for which initialization can fail. This failure might be triggered by invalid initialization parameter values, the absence of a required external resource, or some other condition that prevents initialization from succeeding.

To cope with initialization conditions that can fail, define one or more failable initializers as part of a class, structure, or enumeration definition. You write a failable initializer by placing a question mark after the init keyword (init?).

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}

let cat = Animal(species: "") // nil
let dog = Animal(species: "Mammal") // Animal

A guard statement is used to transfer program control out of a scope if one or more conditions aren’t met. A guard statement has the following form:

guard condition else { statements }

The value of any condition in a guard statement must be of type Bool or a type bridged to Bool. The condition can also be an optional binding declaration, as discussed in Optional Binding. Any constants or variables assigned a value from an optional binding declaration in a guard statement condition can be used for the rest of the guard statement’s enclosing scope. The else clause of a guard statement is required, and must either call a function with the Never return type or transfer program control outside the guard statement’s enclosing scope using one of the following statements:

return break continue throw

/// Example 1
struct MyFood {
    var name: String
    var calories: Int
    
    init?(name: String, calories: Int) {
        guard name != "", calories != 0 else { return nil }
        self.name = name
        self.calories = calories
    }
}

let breakfast = MyFood(name: "banana", calories: 50) // return object
let dinner = MyFood(name: "", calories: 10) // return object
let lunch = MyFood(name: "water", calories: 0) // return object

/// Example 2
func printMyFood(eating: MyFood?) {
    guard let name = eating?.name, let calories = eating?.calories else { fatalError() }
    print(name, calories)
}

printMyFood(eating: breakfast) // print object
printMyFood(eating: lunch) // error

documentation

Returns a sequence of pairs (n, x), where n represents a consecutive integer starting at zero and x represents an element of the sequence.

let emojiArray = ["πŸ₯΅", "😰", "πŸ˜‡", "😱", "πŸ₯Ά"]

for (index, emoji) in emojiArray.enumerated() {
    print(index, emoji)
}

//0 πŸ₯΅
//1 😰
//2 πŸ˜‡
//3 😱
//4 πŸ₯Ά

documentation

Combines elements from another publisher and deliver pairs of elements as tuples.

let emojiArray = ["πŸ₯΅", "πŸ₯Ά", "😎", "😱", "😑"]
let strArray = ["Hot", "Cold", "Good"]
var newArray = [(String, String)]()

for value in zip(emojiArray, strArray) {
    newArray.append(value)
}

print(newArray)

// [("πŸ₯΅", "Hot"), ("πŸ₯Ά", "Cold"), ("😎", "Good")] // Array of Tuple

reduce(into:_:)

Dictionary Reduce

Returns the result of combining the elements of the sequence using the given closure.

let fruits = ["🍏", "πŸ“", "πŸ’", "🍌", "🍏", "πŸ’", "🍌", "🍌", "🍌", "πŸ’", "πŸ’", "🍌", "πŸ“", "πŸ“"]

let fruitsCount = fruits.reduce(into: [:]) { counts, fruit in
    counts[fruit, default: 0] += 1
}

fruitsCount // ["πŸ’": 4, "🍏": 2, "πŸ“": 3, "🍌": 5]
// [String : Int]

Dictionary Search List updateValue(_:forKey:) removeValue(forKey:)

struct Product: Hashable {
    let id: String; // unique identifier
    let name: String;
    let producer: String;

    static func ==(lhs: Product, rhs: Product) -> Bool {
        return lhs.id == rhs.id
    }
}

protocol Library {
    /**
     Adds a new product object to the Library.
     - Parameter product: product to add to the Library
     - Returns: false if the product with same id already exists in the Library, true – otherwise.
    */
    func addNewProduct(product: Product) -> Bool;
    
    /**
     Deletes the product with the specified id from the Library.
     - Returns: true if the product with same id existed in the Library, false – otherwise.
    */
    func deleteProduct(id: String) -> Bool;
    
    /**
     - Returns: 10 product names containing the specified string. If there are several products with the same name, producer's name is appended to product's name.
    */
    func listProductsByName(searchString: String) -> Set<String>;
    
    /**
     - Returns: 10 product names whose producer contains the specified string, ordered by producers.
    */
    func listProductsByProducer(searchString: String) -> [String];
}

class LibraryRepository: Library {
    private var products = [String: Product]()

    func addNewProduct(product: Product) -> Bool {
        return self.products.updateValue(product, forKey: product.id) == nil
    }

    func deleteProduct(id: String) -> Bool {
        return self.products.removeValue(forKey: id) != nil
    }

    func listProductsByName(searchString: String) -> Set<String> {
        return self.products
            .filter({ $1.name.contains(searchString) })
            .prefix(10)
            .reduce(into: Set<String>()) { (productNames, product) in
                if products.filter({ $1.name == product.value.name }).count > 1 {
                    productNames.insert("\(product.value.producer) - \(product.value.name)")
                    return
                }
                productNames.insert(product.value.name)
            }
    }

    func listProductsByProducer(searchString: String) -> [String] {
        return self.products
            .filter({ $1.producer.contains(searchString) })
            .sorted(by: { $0.value.producer > $1.value.producer })
            .prefix(10)
            .map { $0.value.name }
    }
}
enum Difficulty: String {
    typealias DifficultyContent = (title: String, description: String, levels: Int)
    
    case novice
    case warrior
    case master
    case unknown
    
    var content: DifficultyContent {
        switch self {
        case .novice:
            return (title: "Novice", description: "All Too Easy!", levels: 8)
        case .warrior:
            return (title: "Warrior", description: "You Will Die Mortal!", levels: 10)
        case .master:
            return (title: "Master", description: "You Will Never Win!", levels: 12)
        case .unknown:
            return (title: "Unknown", description: "Unknown!", levels: -1)
        }
    }
}

func showTower(for difficulty: Difficulty) {
    print(difficulty.content.title)
    print(difficulty.content.description)
    print(difficulty.content.levels)
}

let difficulty = Difficulty(rawValue: "novice")

showTower(for: difficulty ?? .unknown)

⬆ Back to Index

UIKit

UITextField

class DismissKeyboard: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // When user touch on screen
        let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
        self.view.addGestureRecognizer(tap)
    }
    
    // Dismiss keyboard
    @objc private func dismissKeyboard() {
        self.view.endEditing(true)
    }
}

Data Manager

Documentation - Data Manager
Documentation - Codable
Documentation - Decodable
Documentation - Encodable

class SaveAndLoadDataOnDevice {
    
    // Create an archive file
    var archiveURL: URL? {
        guard let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { fatalError() }
        return documentDirectory.appendingPathComponent("nameFile").appendingPathExtension("plist")
    }
    
    // Decoding an object from an archive
    func loadAll() -> [SameObject]? {
        let decoder = PropertyListDecoder()
        guard let archiveURL = archiveURL else { fatalError() }
        guard let decoderSameObject = try? Data(contentsOf: archiveURL) else { fatalError() }
        return try? decoder.decode([SameObject].self, from: decoderSameObject)
    }
    
    // Encoder the received object into the archive file
    func saveEmojis(_ object: [SameObject]) {
        let encoder = PropertyListEncoder()
        guard let archiveURL = archiveURL else { fatalError() }
        guard let encoderSameObject = try? encoder.encode(object) else { fatalError() }
        try? encoderSameObject.write(to: archiveURL, options: .noFileProtection)
    }
}

// Class have to protocol 'Codable'
class SameObject: Codable {
    
}

⬆ Back to Index

Table View

Documentation

class MoveRow: UITableViewController {
    
    var arrayElement = [String]()
    
    override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        let someRow = arrayElement.remove(at: sourceIndexPath.row)
        arrayElement.insert(someRow, at: destinationIndexPath.row)
    }
}

Documentation1 Documentation2

class DeleteRow: UITableViewController {
    
    var arrayElement = [String]()
    
    override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        true
    }
    
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            arrayElement.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .automatic)
        }
    }
}

Articl - Unwind Segue

Dismissing a View Controller with an Unwind Segue

Configure an unwind segue in your storyboard file that dynamically chooses the most appropriate view controller to display next.

To handle the dismissal of a view controller, create an unwind segue. Unlike the segues that you use to present view controllers, an unwind segue promises the dismissal of the current view controller without promising a specific target for the resulting transition. Instead, UIKit determines the target of an unwind segue programmatically at runtime.

UIKit determines the target of an unwind segue at runtime, so you aren’t restricted in how you set up your view controller hierarchies. Consider a scenario where two view controllers present the same child view controller, as shown in the following figure. You could add complicated logic to determine which view controller to display next, but such a solution wouldn’t scale well. Instead, UIKit provides a simple programmatic solution that scales to any number of view controllers with minimal effort.

@IBAction func myUnwindAction(unwindSegue: UIStoryboardSegue) {
    // Connect a Triggering Object to the Exit Control
}

Documentation

prepare(for:sender:) Notifies the view controller that a segue is about to be performed.

The default implementation of this method does nothing. Subclasses override this method and use it to configure the new view controller prior to it being displayed. The segue object contains information about the transition, including references to both view controllers that are involved.

Because segues can be triggered from multiple sources, you can use the information in the segue and sender parameters to disambiguate between different logical paths in your app. For example, if the segue originated from a table view, the sender parameter would identify the table view cell that the user tapped. You could then use that information to set the data on the destination view controller.

func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    guard segue.identifier == "identifier segue" else { fatalError() }
    guard let secondVC = segue.destination as? SecondViewController else { fatalError() }
        secondVC?.property = "text
}

Parameters

segue The segue object containing information about the view controllers involved in the segue.

sender The object that initiated the segue. You might use this parameter to perform different actions based on which control (or other object) initiated the segue.

Accessing the Segue Attributes

var source: UIViewController The source view controller for the segue.

var destination: UIViewController The destination view controller for the segue.

var identifier: String? The identifier for the segue object.

Documentation

Initiates the segue with the specified identifier from the current view controller's storyboard file.

func performSegue(withIdentifier identifier: String, sender: Any?)

Parameters

identifier The string that identifies the triggered segue. In Interface Builder, you specify the segue’s identifier string in the attributes inspector.

This method throws an Exception handling if there is no segue with the specified identifier.

sender The object that you want to use to initiate the segue. This object is made available for informational purposes during the actual segue.

Discussion

Normally, segues are initiated automatically and not using this method. However, you can use this method in cases where the segue could not be configured in your storyboard file. For example, you might call it from a custom action handler used in response to shake or accelerometer events.

The current view controller must have been loaded from a storyboard. If its storyboard property is nil, perhaps because you allocated and initialized the view controller yourself, this method throws an exception.

Documentation

class Source: UIViewController {
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let source = segue.source as! OtherSource
        source.property = 1
    }
}

class OtherSource: UIViewController {
    var property = 0
}

Documentation

class Destination: UIViewController {
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let destination = segue.destination as! OtherDestination
        destination.property = 1
    }
}

class OtherDestination: UIViewController {
    var property = 0
}
protocol FirstViewControllerDelegate: AnyObject {
    func update(text: String)
}

/// - Implementation option through extensions
//extension FirstViewController: FirstViewControllerDelegate {
//    func update(text: String) {
//        textLabel.text = text
//    }
//}

class FirstViewController: UIViewController, FirstViewControllerDelegate {
    
    @IBOutlet weak var textLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let secondVC = segue.destination as? SecondViewController else { fatalError() }
        secondVC.delegate = self
    }
    
    func update(text: String) {
        textLabel.text = text
    }
}


class SecondViewController: UIViewController {

    @IBOutlet weak var textLabel: UILabel!
    
    weak var delegate: FirstViewControllerDelegate?

    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    @IBAction func actionButton(_ sender: UIButton) {
        delegate?.update(text: "Text was changed")
        dismiss(animated: true, completion: nil)
    }
}

Callbacks

class FirstViewController: UIViewController {
    
    @IBOutlet weak var textLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let secondVC = segue.destination as? SecondViewController else { fatalError() }
        secondVC.closure = { [weak self] text in
            self?.textLabel.text = text
        }
    }
}


class SecondViewController: UIViewController {

    @IBOutlet weak var textLabel: UILabel!
    
    var closure: ((String) -> ())?

    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    @IBAction func actionButton(_ sender: UIButton) {
        closure?("I can pass data by closure")
        dismiss(animated: true, completion: nil)
    }
}
class AutoLayoutConstraintsProgrammatically: UIViewController {

    private let blueView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .link
        return view
    }()

    private let redView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .red
        return view
    }()

    private let imageView_1: UIImageView = {
        let image = UIImage(named: "github")
        let view = UIImageView(image: image)
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    private let imageView_2: UIImageView = {
        let image = UIImage(named: "github")
        let view = UIImageView(image: image)
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        addConstraints()
    }
}

extension AutoLayoutConstraintsProgrammatically {

    private func addConstraints() {
        var constraints = [NSLayoutConstraint]()

        view.addSubview(blueView)
        // Offset from the edges
        constraints.append(blueView.leadingAnchor.constraint(equalTo: view.leadingAnchor))
        constraints.append(blueView.trailingAnchor.constraint(equalTo: view.trailingAnchor))
        constraints.append(blueView.topAnchor.constraint(equalTo: view.topAnchor))
        constraints.append(blueView.bottomAnchor.constraint(equalTo: view.bottomAnchor))

        // Safe Area
//        constraints.append(blueView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor))
//        constraints.append(blueView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor))
//        constraints.append(blueView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor))
//        constraints.append(blueView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor))

        // With constant
//        constraints.append(blueView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 100))
//        constraints.append(blueView.trailingAnchor.constraint(equalTo: view.trailingAnchor,constant: -100))
//        constraints.append(blueView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100))
//        constraints.append(blueView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100))

        view.addSubview(redView)
        // Size Height and Width
        constraints.append(redView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.3))
        constraints.append(redView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.3))

        // Center alignment
        constraints.append(redView.centerXAnchor.constraint(equalTo: view.centerXAnchor))
        constraints.append(redView.centerYAnchor.constraint(equalTo: view.centerYAnchor))

        // Image 1
        view.addSubview(imageView_1)
        constraints.append(imageView_1.heightAnchor.constraint(equalToConstant: 50))
        constraints.append(imageView_1.widthAnchor.constraint(equalToConstant: 50))
        constraints.append(imageView_1.centerXAnchor.constraint(equalTo: view.centerXAnchor))
        constraints.append(imageView_1.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20))

        // Image 2
        view.addSubview(imageView_2)
        imageView_2.heightAnchor.constraint(equalToConstant: 70).isActive = true
        imageView_2.widthAnchor.constraint(equalToConstant: 70).isActive = true
        imageView_2.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        imageView_2.topAnchor.constraint(equalTo: imageView_1.bottomAnchor, constant: 20).isActive = true

        NSLayoutConstraint.activate(constraints)
    }
}

⬆ Back to Index

Features

Watch video
Watch project

class LogoViewController: UIViewController {
    
    // MARK: - Private Properties
    private var imageView: UIImageView = {
        let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 150, height: 150))
        imageView.image = UIImage(named: "logo-icon")
        return imageView
    }()
    
    
    // MARK: - Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(imageView)
        DispatchQueue.main.asyncAfter(deadline: .now()+1.5) {
            self.performSegue(withIdentifier: "welcomeVC", sender: nil)
        }
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        imageView.center = view.center
        DispatchQueue.main.asyncAfter(deadline: .now()+0.5) {
            self.animate()
        }
    }
    
    
    // MARK: - Private Methods
    private func animate() {
        UIView.animate(withDuration: 1.7) {
            let size = self.view.frame.size.width * 2.5
            let diffX = size - self.view.frame.width
            let diffY = self.view.frame.height - size
            
            self.imageView.frame = CGRect(
                x: -(diffX/2),
                y: diffY/2,
                width: size,
                height: size)
            self.imageView.alpha = 0
        }
    }
}
 
 
 class WelcomeViewController: UIViewController {
     
     override func viewDidLoad() {
         super.viewDidLoad()

     }
 }
 

Article

class PersonView: UIView {

    @IBOutlet var contentView: UIView!
    @IBOutlet var firstNameLabel: UILabel!
    @IBOutlet var lastNameLabel: UILabel!
        
    let contentXibName = "PersonView"
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            commonInit()
        }
        
        func commonInit() {
            Bundle.main.loadNibNamed(contentXibName, owner: self, options: nil)
            contentView.fixInView(self)
        }
}

extension UIView
{
    func fixInView(_ container: UIView!) -> Void{
        self.translatesAutoresizingMaskIntoConstraints = false;
        self.frame = container.frame;
        container.addSubview(self);
        NSLayoutConstraint(item: self, attribute: .leading, relatedBy: .equal, toItem: container, attribute: .leading, multiplier: 1.0, constant: 0).isActive = true
        NSLayoutConstraint(item: self, attribute: .trailing, relatedBy: .equal, toItem: container, attribute: .trailing, multiplier: 1.0, constant: 0).isActive = true
        NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: container, attribute: .top, multiplier: 1.0, constant: 0).isActive = true
        NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: container, attribute: .bottom, multiplier: 1.0, constant: 0).isActive = true
    }
}
private func configureSwipe() {
    let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(respondToSwipeGesture))
    swipeRight.direction = .right
    self.view.addGestureRecognizer(swipeRight)
    
    let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(respondToSwipeGesture))
    swipeLeft.direction = .left
    self.view.addGestureRecognizer(swipeLeft)
}

@objc
private func respondToSwipeGesture(gesture: UIGestureRecognizer) {
    if let swipeGesture = gesture as? UISwipeGestureRecognizer {
        switch swipeGesture.direction {
        case .right:
        print("right swipe")
        // add action!
        case .left:
        print("left swipe")
        // add action!
        default:
            break
        }
    }
}

⬆ Back to Index

Design Patterns

Book Design Patterns

Factory Method RU

// structure Factory Method
// MARK: Creator
class FactoryProducts {
    
    static let defaultFactory = FactoryProducts()
    
    func createProduct(_ product: Products) -> Product {
        switch product {
        case .productA: return ConcreteProductA()
        case .productB: return ConcreteProductB()
        }
    }
    
    private init() {}
}

enum Products {
    case productA
    case productB
}

// MARK: Product Interface
protocol Product {
    var id: String { get }
    var name: String { get }
    
    func printProduct()
}


// MARK: Product A
class ConcreteProductA: Product {
    
    var name: String = "Product A"
    var id: String = "1"
    
    func printProduct() {
        print("id: \(id), name: \(name)")
    }
}

// MARK: Product B
class ConcreteProductB: Product {
    
    var name: String = "Product B"
    var id: String = "2"
    
    func printProduct() {
        print("id: \(id), name: \(name)")
    }
}


// MARK: Implementation
class SomeViewController: UIViewController {
    
    var products: [Product] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        addProduct(.productA)
        addProduct(.productB)
        allProducts()
    }
    
    func addProduct(_ product: Products) {
        let newProduct = FactoryProducts.defaultFactory.createProduct(product)
        products.append(newProduct)
    }
    
    func allProducts() {
        products.forEach { $0.printProduct() }
    }
}

⬆ Back to Index

Leet Code

Given an integer numRows, return the first numRows of Pascal's triangle.
In Pascal's triangle, each number is the sum of the two numbers directly above it as shown:

Example 1:
Input: numRows = 5
Output: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]

Example 2:
Input: numRows = 1
Output: [[1]]

func generate(_ numRows: Int) -> [[Int]] {
    
    guard numRows > 0 else { return [] }
    if numRows == 1 { return [[1]] }
    
    var results = [[Int]]()
    results.append([1])
    
    for x in 1..<numRows {
        var newRow = [1]
        let prevRow = results[x - 1]
        
        for j in 1..<prevRow.count {
            let sum = prevRow[j] + prevRow[j - 1]
            newRow.append(sum)
        }
        newRow.append(1)
        results.append(newRow)
    }

    return results
}

Given an m x n matrix, return all elements of the matrix in spiral order.

Example 1:

Input: matrix = [[1,2,3],[4,5,6],[7,8,9]]
Output: [1,2,3,6,9,8,7,4,5]

Example 2:

Input: matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
Output: [1,2,3,4,8,12,11,10,9,5,6,7]

func spiralOrder(_ matrix: [[Int]]) -> [Int] {
    var result = [Int]()
    
    if matrix.count == 0 || matrix[0].count == 0 {
        return result
    }
    
    var left = 0
    var bottom = matrix.count - 1
    var top = 0
    var right = matrix[0].count - 1
    
    while (left <= bottom && top <= right) {
        // Go left to right
        for col in stride(from: top, through: right, by: 1) {
            result.append(matrix[left][col])
        }
        // Go top to down
        left += 1
        for row in stride(from: left, through: bottom, by: 1) {
            result.append(matrix[row][right])
        }
        // Go right to left
        right -= 1
        if left <= bottom {
            for col in stride(from: right, through: top, by: -1) {
                result.append(matrix[bottom][col])
            }
            bottom -= 1
        }
        // Go up
        if top <= right {
            for row in stride(from: bottom, through: left, by: -1) {
                result.append(matrix[row][top])
            }
            top += 1
        }
    }
    return result
}

Amazon iOS Interview Question

You are given an integer array height of length n. There are n vertical lines drawn such that the two endpoints of the ith line are (i, 0) and (i, height[i]).

Find two lines that together with the x-axis form a container, such that the container contains the most water.

Return the maximum amount of water a container can store.

Notice that you may not slant the container.

Example 1:

Input: height = [1,8,6,2,5,4,8,3,7]
Output: 49
Explanation: The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49.

Example 2:

Input: height = [1,1]
Output: 1

func maxArea(_ height: [Int]) -> Int {
    guard !height.isEmpty else { return -1 }
    
    var maxArea = 0
    var left = 0
    var right = height.count - 1
    
    while left < right {
        // Re-calc maxArea
        let minHeight = min(height[left], height[right])
        let currentHeight = minHeight * (right - left)
        maxArea = max(maxArea, currentHeight)

        // Move pointers
        if height[left] < height[right] {
            left += 1
        } else {
            right -= 1
        }
    }
    return maxArea
}

let input = [1, 8, 6, 2, 5, 4, 8, 3, 7]
let result = maxArea(input)
//print("Result: \(result)")

⬆ Back to Index

πŸ›‘οΈ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Support

This project needs a ⭐️ from you. Don't forget to leave a star ⭐️

😎 Contributing

Sergey Lukaschuk βœ‰οΈ [email protected]