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.
finalclassCleanViewController:UIViewController{
// MARK: - IBOutlets
@IBOutletprivate weak varsearchBar:UISearchBar!@IBOutletprivate weak vartableView:UITableView!
// MARK: - Public Properties
varuserID:String?
weak vardelegate:SomeDelegate?
// MARK: - Private Properties
privateletuserService=UserService()privatevaruserList:[User]?
// MARK: - Lifecycle
overridefunc viewDidLoad(){
super.viewDidLoad()setupNavigationBar()}
// MARK: - Private Methods
privatefunc setupNavigationBar(){
navigationController?.navigationBar.backgroundColor =.red
navigationItem.title ="Some"}
// MARK: - IBActions
@IBActionprivatefunc 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
overridefunc viewDidLoad(){
super.viewDidLoad()
navigationController?.navigationBar.backgroundColor =.red
someButton.layer.cornerRadius =10
someButton.layer.masksToBounds =true
navigationItem.title ="Some"print("Some")}✅ Preferred
// MARK: - Lifecycle
overridefunc viewDidLoad(){
super.viewDidLoad()setupNavigationBar()setupSomeButton()printSome()}
// MARK: - Private Methods
privatefunc setupNavigationBar(){
navigationController?.navigationBar.backgroundColor =.red
navigationItem.title ="Some"}privatefunc setupSomeButton(){
someButton.layer.cornerRadius =10
someButton.layer.masksToBounds =true}privatefunc printSome(){print("Some")}Using an extension to implement protocols. Move protocols implementation into extensions with mark // MARK: — SomeProtocol
❌ NOT Preferred
finalclassCleanViewController:UIViewController,UITableViewDataSource,UITableViewDelegate{
// all methods
}✅ Preferred
finalclassCleanViewController:UIViewController{
// class stuff here
}
// MARK: - Table View Data Source
extensionCleanViewController:UITableViewDataSource{func tableView(_ tableView:UITableView, numberOfRowsInSection section:Int)->Int{return userList?.count ??0}func tableView(_ tableView:UITableView, cellForRowAt indexPath:IndexPath)->UITableViewCell{letcell=UITableViewCell()return cell
}}To improve clarity, you need to highlight logically related elements using an empty string.
❌ NOT Preferred
privatefunc 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
privatefunc 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
@UIApplicationMainclassAppDelegate:UIResponder,UIApplicationDelegate{varwindow:UIWindow?func application(
_ application:UIApplication,
didFinishLaunchingWithOptions launchOptions:[UIApplication.LaunchOptionsKey:Any]?)->Bool{
// Override point for customization after application launch.
returntrue}
//
// 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
@UIApplicationMainclassAppDelegate:UIResponder,UIApplicationDelegate{varwindow:UIWindow?func application(
_ application:UIApplication,
didFinishLaunchingWithOptions launchOptions:[UIApplication.LaunchOptionsKey:Any]?)->Bool{returntrue}}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 sourcefeat: (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.
structCard:CustomStringConvertible{vardescription:String{return"\(rank)\(suit)"}varsuit:Suitvarrank:RankenumSuit:String,CustomStringConvertible{vardescription:String{returnself.rawValue }case spades ="♠️"case hearts ="♥️"case clubs ="♣️"case diamonds ="♦️"}enumRank:Int,CustomStringConvertible{case jack =11case lady =12case king =13case ace =14vardescription:String{return"\(self.rawValue)"}}}letcard=Card(suit:.hearts, rank:.king)print(card) // 13 ♥️ nice printstructUser:Equatable{letlogin:Stringletpassword:Intstaticfunc==(lhs:User, rhs:User)->Bool{return lhs.password == rhs.password
}}letuserOne=User(login:"mike", password:123)letuserTwo=User(login:"mikeeeee", password:123)
userOne == userTwo ?print("login"):print("not login")
// loginstructUser:Comparable{letname:Stringletmoney:Intstaticfunc<(lhs:User, rhs:User)->Bool{return lhs.money < rhs.money
}}letmike=User(name:"Mike", money:50)letbob=User(name:"Bob", money:40)
mike < bob // false
mike > bob // trueAn 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).
extensionInt{varrandom:Int{ifself>0{returnInt.random(in:0..<self)}elseifself<0{returnInt.random(in:0..<abs(self))}else{return0}}}// Compare string of numbers
extensionString{staticfunc==(lhs:String, rhs:String)->Bool{return lhs.compare(rhs, options:.numeric)==.orderedSame
}staticfunc<(lhs:String, rhs:String)->Bool{return lhs.compare(rhs, options:.numeric)==.orderedAscending
}staticfunc<=(lhs:String, rhs:String)->Bool{return lhs.compare(rhs, options:.numeric)==.orderedAscending || lhs.compare(rhs, options:.numeric)==.orderedSame
}staticfunc>(lhs:String, rhs:String)->Bool{return lhs.compare(rhs, options:.numeric)==.orderedDescending
}staticfunc>=(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 !=0else{throwNSError(domain:"The number 'b' must not be zero", code:100)}returnDouble(a / b)}
/// Checks in 'do catch'
do{trydivide(10, by:0)}catchlet error{
error.localizedDescription
}
/// Checks in 'try!' or 'try?'
letmyError=try!divide(10, by:0)letmyError=try?divide(10, by:0)
/// Example with user data
enumNetworkError:Error{case notPage
case networkError
}classNetwork{staticletresponses=[200,404,500]staticfunc request()->Int{return responses.randomElement()??0}}classNetworkManager{func userRequest(text:String)throws->String{letstatusCode=Network.request()guard statusCode !=404else{throwNetworkError.notPage }guard statusCode !=500else{throwNetworkError.networkError }return"\(statusCode): image \(text)"}}classBrowser{letnetworkManager=NetworkManager()func getImage(text:String){do{letresult=try networkManager.userRequest(text: text)print(result)}catch{switch error {caseNetworkError.notPage:print("Not Page")caseNetworkError.networkError:print("Network Error")default:print(error.localizedDescription)}}}}letsafari=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
letunknown:Any="Steve"iflet 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`.
classSuperclass{varname:String="Tom"}classSubclass:Superclass{varage:Int=15}lettom:Superclass=Subclass() // The variable type of 'Superclass' assigns an instance of 'Subclass'
tom.age // error
iflet 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?).
structAnimal{letspecies:Stringinit?(species:String){if species.isEmpty {returnnil}self.species = species
}}letcat=Animal(species:"") // nil
letdog=Animal(species:"Mammal") // AnimalA 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:
returnbreakcontinuethrow
/// Example 1
structMyFood{varname:Stringvarcalories:Intinit?(name:String, calories:Int){guard name !="", calories !=0else{returnnil}self.name = name
self.calories = calories
}}letbreakfast=MyFood(name:"banana", calories:50) // return object
letdinner=MyFood(name:"", calories:10) // return object
letlunch=MyFood(name:"water", calories:0) // return object
/// Example 2
func printMyFood(eating:MyFood?){guardlet name = eating?.name,let calories = eating?.calories else{fatalError()}print(name, calories)}printMyFood(eating: breakfast) // print object
printMyFood(eating: lunch) // errorReturns a sequence of pairs (n, x), where n represents a consecutive integer starting at zero and x represents an element of the sequence.
letemojiArray=["🥵","😰","😇","😱","🥶"]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.
letemojiArray=["🥵","🥶","😎","😱","😡"]letstrArray=["Hot","Cold","Good"]varnewArray=[(String, String)]()forvalueinzip(emojiArray, strArray){
newArray.append(value)}print(newArray)
// [("🥵", "Hot"), ("🥶", "Cold"), ("😎", "Good")] // Array of TupleDictionary Reduce
Returns the result of combining the elements of the sequence using the given closure.
letfruits=["🍏","🍓","🍒","🍌","🍏","🍒","🍌","🍌","🍌","🍒","🍒","🍌","🍓","🍓"]letfruitsCount= fruits.reduce(into:[:]){ counts, fruit incounts[fruit, default:0]+=1}
fruitsCount // ["🍒": 4, "🍏": 2, "🍓": 3, "🍌": 5]
// [String : Int]Dictionary Search ListupdateValue(_:forKey:)removeValue(forKey:)
structProduct:Hashable{letid:String; // unique identifier
letname:String;
letproducer:String;
staticfunc==(lhs:Product, rhs:Product)->Bool{return lhs.id == rhs.id
}}protocolLibrary{
/**
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];
}classLibraryRepository:Library{privatevarproducts=[String: Product]()func addNewProduct(product:Product)->Bool{returnself.products.updateValue(product, forKey: product.id)==nil}func deleteProduct(id:String)->Bool{returnself.products.removeValue(forKey: id)!=nil}func listProductsByName(searchString:String)->Set<String>{returnself.products
.filter({ $1.name.contains(searchString)}).prefix(10).reduce(into:Set<String>()){(productNames, product)inif 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]{returnself.products
.filter({ $1.producer.contains(searchString)}).sorted(by:{ $0.value.producer > $1.value.producer }).prefix(10).map{ $0.value.name }}}enumDifficulty:String{typealiasDifficultyContent=(title:String, description:String, levels:Int)case novice
case warrior
case master
case unknown
varcontent:DifficultyContent{switchself{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)}letdifficulty=Difficulty(rawValue:"novice")showTower(for: difficulty ??.unknown)classDismissKeyboard:UIViewController{overridefunc viewDidLoad(){
super.viewDidLoad()
// When user touch on screen
lettap=UITapGestureRecognizer(target:self, action: #selector(dismissKeyboard))self.view.addGestureRecognizer(tap)}
// Dismiss keyboard
@objcprivatefunc dismissKeyboard(){self.view.endEditing(true)}}Documentation - Data Manager
Documentation - Codable
Documentation - Decodable
Documentation - Encodable
classSaveAndLoadDataOnDevice{
// Create an archive file
vararchiveURL:URL?{guardlet 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]?{letdecoder=PropertyListDecoder()guardlet archiveURL = archiveURL else{fatalError()}guardlet decoderSameObject =try?Data(contentsOf: archiveURL)else{fatalError()}returntry? decoder.decode([SameObject].self, from: decoderSameObject)}
// Encoder the received object into the archive file
func saveEmojis(_ object:[SameObject]){letencoder=PropertyListEncoder()guardlet archiveURL = archiveURL else{fatalError()}guardlet encoderSameObject =try? encoder.encode(object)else{fatalError()}try? encoderSameObject.write(to: archiveURL, options:.noFileProtection)}}
// Class have to protocol 'Codable'
classSameObject:Codable{}classMoveRow:UITableViewController{vararrayElement=[String]()overridefunc tableView(_ tableView:UITableView, moveRowAt sourceIndexPath:IndexPath, to destinationIndexPath:IndexPath){letsomeRow= arrayElement.remove(at: sourceIndexPath.row)
arrayElement.insert(someRow, at: destinationIndexPath.row)}}classDeleteRow:UITableViewController{vararrayElement=[String]()overridefunc tableView(_ tableView:UITableView, canEditRowAt indexPath:IndexPath)->Bool{true}overridefunc 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.
@IBActionfunc 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()}guardlet secondVC = segue.destination as?SecondViewControllerelse{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.
classSource:UIViewController{overridefunc prepare(for segue:UIStoryboardSegue, sender:Any?){letsource= segue.source as!OtherSource
source.property =1}}classOtherSource:UIViewController{varproperty=0}classDestination:UIViewController{overridefunc prepare(for segue:UIStoryboardSegue, sender:Any?){letdestination= segue.destination as!OtherDestination
destination.property =1}}classOtherDestination:UIViewController{varproperty=0}protocolFirstViewControllerDelegate:AnyObject{func update(text:String)}
/// - Implementation option through extensions
//extension FirstViewController: FirstViewControllerDelegate {
// func update(text: String) {
// textLabel.text = text
// }
//}
classFirstViewController:UIViewController,FirstViewControllerDelegate{@IBOutlet weak vartextLabel:UILabel!overridefunc viewDidLoad(){
super.viewDidLoad()}overridefunc prepare(for segue:UIStoryboardSegue, sender:Any?){guardlet secondVC = segue.destination as?SecondViewControllerelse{fatalError()}
secondVC.delegate =self}func update(text:String){
textLabel.text = text
}}classSecondViewController:UIViewController{@IBOutlet weak vartextLabel:UILabel!
weak vardelegate:FirstViewControllerDelegate?overridefunc viewDidLoad(){
super.viewDidLoad()}@IBActionfunc actionButton(_ sender:UIButton){
delegate?.update(text:"Text was changed")dismiss(animated:true, completion:nil)}}classFirstViewController:UIViewController{@IBOutlet weak vartextLabel:UILabel!overridefunc viewDidLoad(){
super.viewDidLoad()}overridefunc prepare(for segue:UIStoryboardSegue, sender:Any?){guardlet secondVC = segue.destination as?SecondViewControllerelse{fatalError()}
secondVC.closure ={[weak self] text inself?.textLabel.text = text
}}}classSecondViewController:UIViewController{@IBOutlet weak vartextLabel:UILabel!varclosure:((String)->())?overridefunc viewDidLoad(){
super.viewDidLoad()}@IBActionfunc actionButton(_ sender:UIButton){closure?("I can pass data by closure")dismiss(animated:true, completion:nil)}}classAutoLayoutConstraintsProgrammatically:UIViewController{privateletblueView:UIView={letview=UIView()
view.translatesAutoresizingMaskIntoConstraints =false
view.backgroundColor =.link
return view
}()privateletredView:UIView={letview=UIView()
view.translatesAutoresizingMaskIntoConstraints =false
view.backgroundColor =.red
return view
}()privateletimageView_1:UIImageView={letimage=UIImage(named:"github")letview=UIImageView(image: image)
view.translatesAutoresizingMaskIntoConstraints =falsereturn view
}()privateletimageView_2:UIImageView={letimage=UIImage(named:"github")letview=UIImageView(image: image)
view.translatesAutoresizingMaskIntoConstraints =falsereturn view
}()overridefunc viewDidLoad(){
super.viewDidLoad()addConstraints()}}extensionAutoLayoutConstraintsProgrammatically{privatefunc addConstraints(){varconstraints=[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 =trueNSLayoutConstraint.activate(constraints)}}classLogoViewController:UIViewController{
// MARK: - Private Properties
privatevarimageView:UIImageView={letimageView=UIImageView(frame:CGRect(x:0, y:0, width:150, height:150))
imageView.image =UIImage(named:"logo-icon")return imageView
}()
// MARK: - Lifecycle
overridefunc viewDidLoad(){
super.viewDidLoad()
view.addSubview(imageView)DispatchQueue.main.asyncAfter(deadline:.now()+1.5){self.performSegue(withIdentifier:"welcomeVC", sender:nil)}}overridefunc viewDidLayoutSubviews(){
super.viewDidLayoutSubviews()
imageView.center = view.center
DispatchQueue.main.asyncAfter(deadline:.now()+0.5){self.animate()}}
// MARK: - Private Methods
privatefunc animate(){UIView.animate(withDuration:1.7){letsize=self.view.frame.size.width *2.5letdiffX= size -self.view.frame.width
letdiffY=self.view.frame.height - size
self.imageView.frame =CGRect(
x:-(diffX/2),
y: diffY/2,
width: size,
height: size)self.imageView.alpha =0}}}classWelcomeViewController:UIViewController{overridefunc viewDidLoad(){
super.viewDidLoad()}}classPersonView:UIView{@IBOutletvarcontentView:UIView!@IBOutletvarfirstNameLabel:UILabel!@IBOutletvarlastNameLabel:UILabel!letcontentXibName="PersonView"overrideinit(frame:CGRect){
super.init(frame: frame)commonInit()}requiredinit?(coder aDecoder:NSCoder){
super.init(coder: aDecoder)commonInit()}func commonInit(){Bundle.main.loadNibNamed(contentXibName, owner:self, options:nil)
contentView.fixInView(self)}}extensionUIView{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 =trueNSLayoutConstraint(item:self, attribute:.trailing, relatedBy:.equal, toItem: container, attribute:.trailing, multiplier:1.0, constant:0).isActive =trueNSLayoutConstraint(item:self, attribute:.top, relatedBy:.equal, toItem: container, attribute:.top, multiplier:1.0, constant:0).isActive =trueNSLayoutConstraint(item:self, attribute:.bottom, relatedBy:.equal, toItem: container, attribute:.bottom, multiplier:1.0, constant:0).isActive =true}}privatefunc configureSwipe(){letswipeRight=UISwipeGestureRecognizer(target:self, action: #selector(respondToSwipeGesture))
swipeRight.direction =.right
self.view.addGestureRecognizer(swipeRight)letswipeLeft=UISwipeGestureRecognizer(target:self, action: #selector(respondToSwipeGesture))
swipeLeft.direction =.left
self.view.addGestureRecognizer(swipeLeft)}@objcprivatefunc respondToSwipeGesture(gesture:UIGestureRecognizer){iflet 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
classFactoryProducts{staticletdefaultFactory=FactoryProducts()func createProduct(_ product:Products)->Product{switch product {case.productA:returnConcreteProductA()case.productB:returnConcreteProductB()}}privateinit(){}}enumProducts{case productA
case productB
}
// MARK: Product Interface
protocolProduct{varid:String{get}varname:String{get}func printProduct()}
// MARK: Product A
classConcreteProductA:Product{varname:String="Product A"varid:String="1"func printProduct(){print("id: \(id), name: \(name)")}}
// MARK: Product B
classConcreteProductB:Product{varname:String="Product B"varid:String="2"func printProduct(){print("id: \(id), name: \(name)")}}
// MARK: Implementation
classSomeViewController:UIViewController{varproducts:[Product]=[]overridefunc viewDidLoad(){
super.viewDidLoad()addProduct(.productA)addProduct(.productB)allProducts()}func addProduct(_ product:Products){letnewProduct=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 >0else{return[]}if numRows ==1{return[[1]]}varresults=[[Int]]()
results.append([1])forxin1..<numRows {varnewRow=[1]letprevRow=results[x -1]forjin1..<prevRow.count {letsum=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]{varresult=[Int]()if matrix.count ==0 || matrix[0].count ==0{return result
}varleft=0varbottom= matrix.count -1vartop=0varright=matrix[0].count -1while(left <= bottom && top <= right){
// Go left to right
forcolinstride(from: top, through: right, by:1){
result.append(matrix[left][col])}
// Go top to down
left +=1forrowinstride(from: left, through: bottom, by:1){
result.append(matrix[row][right])}
// Go right to left
right -=1if left <= bottom {forcolinstride(from: right, through: top, by:-1){
result.append(matrix[bottom][col])}
bottom -=1}
// Go up
if top <= right {forrowinstride(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}varmaxArea=0varleft=0varright= height.count -1while left < right {
// Re-calc maxArea
letminHeight=min(height[left],height[right])letcurrentHeight= minHeight *(right - left)
maxArea =max(maxArea, currentHeight)
// Move pointers
ifheight[left]<height[right]{
left +=1}else{
right -=1}}return maxArea
}letinput=[1,8,6,2,5,4,8,3,7]letresult=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





