In this project, I have collected various best practices and iOS development tips. This is my personal development assistant.
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
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'
- 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
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
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
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
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 } }
- Error handler, using the
throwskeyword we indicate that the function can receive an error. - Using the
throwkey we throw an error into theNSErrorinstance. 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
trykeyword. - 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
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 π₯Ά
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
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)
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) } }
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 { }
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) } }
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) } } }
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 }
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.
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.
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 }
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) } }
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) } }
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() } }
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 } } }
// 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() } } }
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)")
This project is licensed under the MIT License - see the LICENSE file for details.
This project needs a βοΈ from you. Don't forget to leave a star βοΈ
Sergey Lukaschuk βοΈ s.lukaschuk@yahoo.com