Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
/ Help Public

This is my personal development assistant. 😎

License

Notifications You must be signed in to change notification settings

lgreydev/Help

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

135 Commits

Repository files navigation

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 βœ‰οΈ s.lukaschuk@yahoo.com

About

This is my personal development assistant. 😎

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /