Upgrade to Pro — share decks privately, control downloads, hide ads and more …

iOS App Development (non SwiftUI)

iOS App Development (non SwiftUI)

Jussi Pohjolainen

October 29, 2020
Tweet

More Decks by Jussi Pohjolainen

Other Decks in Technology

Transcript

  1. Different Approaches Swift / Objective-C UIKit + Storyboard UIKit +

    Just Code (Snapkit /Purelayout) SwiftUI Swift Learn the language first! UIKit + XIBs
  2. UIKit + Storyboards / XIB • Autolayout can be frustrating

    • Version control can be cumbersome • Debug can be hard • Use several storyboards! Split your app. • Good approach for small apps • Number of developers... 1?
  3. UIKit + Code Everything • "Full control of everything" •

    Use libraries like snapkit or purelayout for easier coding • Good approach if you want control every aspect • But this will require lot of coding!
  4. SwiftUI • The future of Apple Devices development • It's

    cross platform! The UI code should be reusable on all devices. • No backward compatibility (only iOS 13 +) • Hot Reload can be frustrating • Much less documentation available • Good approach to new apps • Way easier than storyboards
  5. AppDelegate vs SceneDelegate – iOS13 • App Delegate manages your

    app process • Configure your initial scenes • Process events and lifecycle • Scene Delegate manages one instance of your app's user interface • So if the user has created two windows showing your app, you have two scenes, both backed by the same app delegate • Your app no longer moves to background, scenes do! • UI events and lifecycle
  6. AppDelegate • Main entry point for your application • Several

    application level lifecycle events • AppDelegate role has changed from iOS 12 -> 13 • In the default template you will find three (3) methods • Whan app is opened • application(_:didFinishLaunchingWithOptions:) • When creating new scenes (it's basically a window) • Discards a scene • Notice that in addition to these, there are other lifecycle events (when app terminates for example)
  7. @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication,

    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { NSLog("AppDelegate: First launch") return true } func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { NSLog("AppDelegate: New Scene created") return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) { NSLog("AppDelegate: Scene discarded") } } Generates trivial main function that starts the app
  8. @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication,

    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { NSLog("AppDelegate: First launch") return true } func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { NSLog("AppDelegate: New Scene created") return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) { NSLog("AppDelegate: Scene discarded") } } First launch
  9. Scene • App can have more than one scene •

    Allows to build multi-window apps on iOS and iPadOS • App Delegate has functions related to the management of scene sessions • application(_:configurationForConnecting:options:) • Needs a return configuration object when creating a new scene • Can be used to restore the state of a scene • application(_:didDiscardSceneSessions:) • Called when app's user closed one or more scene via the app switcher (according to the doc)
  10. @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication,

    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { NSLog("AppDelegate: First launch") return true } func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { NSLog("AppDelegate: New Scene created") return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) { NSLog("AppDelegate: Scene discarded") } } when new scenes are created. Returns configuration object that hold information which storyboard to use (info.plist). You can decide which configuration to use. When the user removes a scene from the app switcher. If your app is not running, UIKit calls this method the next time your app launches.
  11. SceneDelegate import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window:

    UIWindow? Everything seen to the end user is put into a window object
  12. import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { NSLog("Creates Window object") guard let _ = (scene as? UIWindowScene) else { return } } func sceneDidDisconnect(_ scene: UIScene) { NSLog("Scene disconnected!") } func sceneDidBecomeActive(_ scene: UIScene) { NSLog("Active!") } func sceneWillResignActive(_ scene: UIScene) { NSLog("From foreground to background") } func sceneWillEnterForeground(_ scene: UIScene) { NSLog("Will enter foreground") } func sceneDidEnterBackground(_ scene: UIScene) { NSLog("Did enter background") } }
  13. Lab

  14. Concepts • Window - UIWindow • Basic container for application's

    views. Window's view is the visible part in the screen. • To add something to the screen, it's done using the UIWindow object. • View - UIView • Define a portion of a window that fills some content. Can be image, text or shape. • UIKit provides predefined views that you can use: UIButton, UILabel etc. • View can be a container for other views • View Controller - UIViewController • Class that acts controller between the model and view
  15. Window • Window (UIWindow) handles the overall presentation of your

    app's user interface • Windows work with views (usually through view controllers) • After window is created, it stays the same and only the views displayed by it change • Every iOS app has at least one Window • If need for an external display (for example Apple TV) then another Window object is used. • To display a view in window, add the view as an subview of window!
  16. View • Building blocks for the UI • View is

    an instance of UIView (or one of it's subclasses) • Responsible for • drawing content • handling touch events • managing layout for subviews • Parent view (superview) is responsible for positioning and sizing their child views (subviews) • Changing the superview you may influence the subviews, position for example
  17. Creating UIWindow class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) window.backgroundColor = UIColor.yellow self.window = window window.makeKeyAndVisible() } }
  18. Creating UIView // Let's define position and size for label

    let frame = CGRect(x: 0, y: 0, width: 100, height: 100) // Create label let label = UILabel(frame: frame) label.text = "hello" label.backgroundColor = UIColor.darkGray label.textColor = UIColor.white
  19. Setting up Window: Just Code if let windowScene = scene

    as? UIWindowScene { let window = UIWindow(windowScene: windowScene) window.backgroundColor = UIColor.yellow // Let's define position and size for label let frame = CGRect(x: 0, y: 0, width: 100, height: 100) // Create label let label = UILabel(frame: frame) label.text = "hello" label.backgroundColor = UIColor.darkGray label.textColor = UIColor.white let viewController = UIViewController() viewController.view.addSubview(label) window.rootViewController = viewController self.window = window window.makeKeyAndVisible() }
  20. Lab

  21. About View Controllers • View Controllers are a link between

    app's data and visual appearance • Displayed content should go always through a view controller (content view controller) • When implementing iOS app you have multiple view controller's for each screen. • View Controller can be also a container view controller • A view controller holding other view controllers! • Several built-in content view controllers available, like navigation or tab view controller
  22. View Controllers, Xib, Storyboard? • We built our app using

    Model – View – Controller • Model -> Holds data of your app • View -> either .xib or .storyboard • Controller -> controls the model and the view • When creating a new "screen" to our app, you typically have 1.Controller class 2.View either in .xib or in .storyboard 28.10.2020 ESITYKSEN NIMI / TEKIJÄ 32
  23. Word about Storyboards • Storyboard is a feature in iOS

    5 -> where you can design and build a UI for your apps. • One file may contain several screens! • Good • Better overview of all the screens in your app • Describes transitions between screens, simply ctrl-drag from screen to another to create "segue" • Storyboards are nice for apps with small or medium number of screens • Bad • Storyboard will get very confusing if you have lot of screens. • You need a big screen • Apple seems to be pushing the storyboard approach • Apple seems to be pushing towards SwiftUI
  24. View Controller manages Views • Each View Controller organizes and

    controls it's view. • The view is often a "root view" containing other views • View Controller is usually your own class, extending UIViewController • When setting some view controller as a rootViewController of the UIWindow, then window will automatically add the view controller's view to subview (and visible to screen.) • When you have storyboard declared in info.plist 1. main.storyboard file is opened 2. it's initial view controller is set as root view controller 3. root view controllers view is added to window
  25. Implementing a View Controller • To implement a view controller,

    you subclass UIViewController • The View Controller controls all the views in that "screen" • View Controller usually responds to user events happening to those views • Button presses, touches and so on • Usually the views are designed using Interface builder.
  26. Overriding methods • You can override UIViewController's methods • viewDidLoad

    • allocate and load data to be displayed in view • viewDidAppear • when view is visible • viewDidDisappear • when view is not visible • didReceiveMemoryWarning • Respond to low memory notifications
  27. Storyboard • Storyboard holds visual presentation of all your views

    and view controllers • When creating your template app, you have storyboard holding your view controller and view. • From identity inspector you can see that the view controller is linked to a file called ViewController.swift • From attributes inspector you can simulate different screen sizes
  28. Outlet: Connect from Code to View • Outlet is a

    variable that is annotated with the symbol IBOutlet • The value of the outlet is specified graphically in a storyboard • Example: declare a UILabel as an outlet • @IBOutlet weak var myButton: UIButton! • Drag UIButton to Storyboard and ctrl-drag to ViewController - code Demo
  29. Target-Action • Target Action is a design pattern in an

    object holds information necessary to send to another object when an event occurs. • "Which method is called when button is clicked?" • An action method must have a certain form: • @IBAction func buttonClicked(sender: AnyObject) {..} • You can set target and action in code or using Interface Builder Demo
  30. Auto Layout • Auto Layout is a system that let's

    you lay out app's UI elements creating relationships between elements • These relationships are called constraints • You can create constraints to an element or a group of elements • Basic idea is to create UI that works on different screen sizes and orientation 28.10.2020 ESITYKSEN NIMI / TEKIJÄ 40
  31. Constraint • Fundamental building block in auto layout is Constraint

    • Rules about how to layout elements • Examples • specify element width • specify horizontal distance from another element • Auto Layout calculates all constraints at the same time and tries to layout your UI 28.10.2020 ESITYKSEN NIMI / TEKIJÄ 41
  32. Working with Constraints • You can add constraints in several

    ways • Control-drag • Drag from view to view and select the constraint you want to use • Align and Pin Menus • On the bottom of the canvas, you can see a small icon group. Select either align or pin 28.10.2020 ESITYKSEN NIMI / TEKIJÄ 42
  33. UIWindow • Remember that everything visible in the screen goes

    through UIWindow • UIWindow has a property rootViewController that provides the content view of the visible window. • You can change this at runtime in view controller • self.view.window?.rootViewController = SecondScreenController(nibName: "SecondScreen", bundle: nil)
  34. Container View Controller • Usually we don't just "change screens".

    • Problem is that we have to define navigation programmatically. How user is coming back? What kind of visual cues are we giving to the user about the navigation? • Instead it's good practice to use container view controllers • Container view controller contains other view controllers • UINavigationController • UITabController • There are couple ready made content view controllers, but you can also build our own.
  35. About Navigation Stack • The Navigation controller manages currently displayed

    screens using a navigation stack • Bottom stack: root view controller • Top of the stack: view controller that is displayed currently • To add a new screen: • pushViewController:animated: • To remove the displayed screen • popViewControllerAnimated: • Navigation controller provides you a back button, that pops the current view
  36. Example using the Navigation func scene(_ scene: UIScene, willConnectTo session:

    UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { let win = UIWindow(windowScene: scene as! UIWindowScene) let root = FirstScreenController(nibName: "FirstScreen", bundle: nil) root.title = "First screen" let navController = UINavigationController(rootViewController: root) win.rootViewController = navController self.window = win win.makeKeyAndVisible() }
  37. Push class FirstScreenController : UIViewController { var secondScreen : SecondScreenController?

    = nil @IBAction func changeScreen(_ sender: Any) { if self.secondScreen == nil { self.secondScreen = SecondScreenController(nibName: "SecondScreen", bundle: nil) } self.navigationController!.pushViewController(self.secondScreen!, animated: true) } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } }
  38. Lazy loading class FirstScreenController : UIViewController { lazy var secondScreen

    : SecondScreenController = SecondScreenController(nibName: "SecondScreen", bundle: nil) @IBAction func changeScreen(_ sender: Any) { self.navigationController!.pushViewController(self.secondScreen, animated: true) } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } }
  39. Using Storyboard • Create your navigation using Interface Builder –

    no code! • How? • Select your root view controller • Choose Menu: Editor » Embed in » Navigation Controller • Now your view controller is embedded inside of a navigation controller! • Create new view controller to storyboard • Create button to first view controller and ctrl-drag to the new view controller. Choose push and you now have a new navigation from view to another!
  40. Segues • Show • Pushes the destination view controller onto

    the navigation stack, moving the source view controller out of the way providing a back button to navigate back to the source – on all devices • Show Detail • Replaces the detail/secondary view controller when in a UISplitViewController with no ability to navigate back to the previous view controller. Example: Mail in iPad in landscape when tapping mail title. • Present Modally • Presents a view controller in various different ways as defined by the Presentation option, covering up the previous view controller - most commonly used to present a view controller that animates up from the bottom and covers the entire screen on iPhone • Present as popover • When run on iPad, the destination appears in a small popover, and tapping anywhere outside of this popover will dismiss it.
  41. Example about Passing Data override func prepare(for _segue: UIStoryboardSegue, sender:

    Any?) { let destViewController = _segue.destination as! SecondScreenStoryboardController destViewController.stuff = "Some Data For You!" }
  42. And receiving data class SecondScreenStoryboardController: UIViewController { var stuff :

    String = "" override func viewDidLoad() { super.viewDidLoad() self.title = "Second Screen" } override func viewDidAppear(_ animated: Bool) { print(stuff) } }
  43. Should Segue open? • If you want to decide if

    segue should perform, add shouldPerformSegueWithIdentifier - method to your view controller • This method is called before the segue should start. • In the method, return true if you want to open the next View Controller override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { return false }
  44. UITabBarController • A tab bar controller is a container view

    controller that you use to divide your app into two or more distinct modes of operation • Tab bar holds multiple tabs, each represented by a child view controller • Selecting the tab causes the view controller's view to be displayed on the screen
  45. Objects of the Tab Bar • Standard Tab Bar interface

    consists of • UITabBarController object • One content view controller for each tab • Really easy to use • Allocate the UITabController • Use viewControllers property to set the custom view controllers • Add the UITabController to be rootViewController • Or use Storyboard
  46. Example let tabs = UITabBarController() let a = Tab1Controller(nibName: "Tab1Controller",

    bundle: nil) a.title = "First Tab" let b = Tab2Controller(nibName: "Tab2Controller", bundle: nil) b.title = "Second Tab" tabs.viewControllers = [a, b] self.window?.rootViewController = tabs
  47. Modifying Tab Title and Image • To set the tab

    title and image, you do this in your custom view controller • The UIViewController holds a property tabBarItem (UITabBarItem) that you can use to change the current tab bar • The default title of the tab is the title of the view controller. • So changing the title of view controller will influence the tab to have a title also!
  48. Mixing Container View Controllers • It's possible to mix container

    view controllers • For example, tab controller can hold navigation controller
  49. Classes Needed • Table View • UITableView • Manage selections,

    scroll the table view, insert or delete rows and selections • UITableView inherites UIScrollView, defines scrolling as default • Table View Controller • UITableViewController • Adds support for many standard table related behaviors. Minimize the amount of code you have to write. • Subclass UITableViewController • Data Source and Delegate • UITableView must have delegate and data source: UITableViewDataSource and UITableViewDelegate
  50. UITableViewDelegate • Lot’s of options: • Configuring Rows for the

    Table View • Managing Selections • Modifying Header and Footer • Editing Table Rows • Clicking a Row • See UITableViewDelegate documentation for all methods, all optional functions
  51. Without UITableViewController class MyTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { var dummyData

    = ["Luke Skywalker", "R2-D2"] @IBOutlet weak var tableview: UITableView! override func viewDidLoad() { super.viewDidLoad() self.tableview.delegate = self self.tableview.dataSource = self } ... } This data will be displayed tableview is in xib let's pass delegate and datasource as self. This class must conform to the protocols
  52. Data Source functions func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)

    -> Int { return dummyData.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { var cell : UITableViewCell? = tableView.dequeueReusableCell(withIdentifier: "mickeymouse") if cell == nil { cell = UITableViewCell(style: UITableViewCell.CellStyle.subtitle, reuseIdentifier: "mickeymouse") } cell?.textLabel?.text = dummyData[indexPath.row] return cell! } Number of rows Must return UITableViewCell. The text is fetch from the array. Reuses cell objects
  53. UITableViewController class MyTableViewController2: UITableViewController { var dummyData = ["Luke Skywalker",

    "R2-D2"] override func viewDidLoad() { super.viewDidLoad() } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dummyData.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { var cell : UITableViewCell? = tableView.dequeueReusableCell(withIdentifier: "mickeymouse") if cell == nil { cell = UITableViewCell(style: UITableViewCell.CellStyle.subtitle, reuseIdentifier: "mickeymouse") } cell?.textLabel?.text = dummyData[indexPath.row] return cell! } } conforms to the protocols, sets the delegate and datasource. Has tableView property If we want we can create the cell also in interface builder
  54. Usage class MyTableViewController2: UITableViewController { var dummyData = ["Luke Skywalker",

    "R2-D2"] override func viewDidLoad() { super.viewDidLoad() let xibfile = UINib(nibName: "mycell", bundle: nil) self.tableView.register(xibfile, forCellReuseIdentifier: "mikkihiiri") } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dummyData.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let secondCell = tableView.dequeueReusableCell(withIdentifier: "mikkihiiri")! secondCell.textLabel?.text = dummyData[indexPath.row] return secondCell } } Register the separate xib file And then we can just use it without if ...
  55. Storyboard By using storyboard you will have the cell xib

    embedded here. No need for external xibs
  56. URLSession • For simple requests, use URLSession.shared object • When

    receiving stuff, you will get NSData object • It is asynchronous • The URLSession.shared has method func dataTask(with: URL, completionHandler: (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
  57. Example let myURL = URL(string: "https://pohjus-rest-location.herokuapp.com/locations")! let httpTask = URLSession.shared.dataTask(with:

    myURL) { (optionalData, response, error) in print("done fetching") } httpTask.resume() NSData? URLResponse? Error?
  58. Example: Using Response let httpTask = URLSession.shared.dataTask(with: myURL) {(optionalData, response,

    error) in if let httpResponse = response as? HTTPURLResponse { print("statusCode: \(httpResponse.statusCode)") } } HTTPURLResponse is a subclass of URLResponse
  59. Example: Data -> String let httpTask = URLSession.shared.dataTask(with: myURL) {(optionalData,

    response, error) in let data : String = String(data: optionalData!, encoding: .utf8)! print(data) }
  60. With View Controller override func viewDidAppear(_ animated: Bool) { self.fetch(url:

    "https://pohjus-rest-location.herokuapp.com/locations") } func fetch(url: String) { let myURL = URL(string: url)! let httpTask = URLSession.shared.dataTask(with: myURL) { (optionalData, response, error) in self.parse(data: optionalData) } httpTask.resume() } func parse(data: Data?) { if let d = data { print(String(data: d, encoding: String.Encoding.utf8)!) } } When view appears start fetching When view appears start fetching and call parse Outputs data as String
  61. Example let jsonDecoder = JSONDecoder() do { let stuff =

    try jsonDecoder.decode(Array<Location>.self, from: optionalData!) print(stuff) } catch let error { print(error) } Custom struct, must conform to Codeable
  62. Example override func viewDidAppear(_ animated: Bool) { self.fetch(url: "https://pohjus-rest-location.herokuapp.com/locations") }

    func parse(data: Data?) { if let d = data { let jsonDecoder = JSONDecoder() do { let stuff = try jsonDecoder.decode(Array<Location>.self, from: d) print(stuff) } catch let error { print(error) } } } func fetch(url: String) { let myURL = URL(string: url)! let httpTask = URLSession.shared.dataTask(with: myURL) { (optionalData, response, error) in self.parse(data: optionalData) } httpTask.resume() } Now we have an array full of location objects
  63. Threading • it's never OK to do user interface work

    on the background thread. • If you're on a background thread and want to execute code on the main thread • DispatchQueue.main.async { UI STUFF HERE }
  64. class MyTableViewController2: UITableViewController { var dummyData : Array<Location> = []

    override func viewDidLoad() { super.viewDidLoad() let xibfile = UINib(nibName: "mycell", bundle: nil) self.tableView.register(xibfile, forCellReuseIdentifier: "mikkihiiri") } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dummyData.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let secondCell = tableView.dequeueReusableCell(withIdentifier: "mikkihiiri")! secondCell.textLabel?.text = "\(dummyData[indexPath.row].lat) - \(dummyData[indexPath.row].lon)" return secondCell } override func viewDidAppear(_ animated: Bool) { self.fetch(url: "https://pohjus-rest-location.herokuapp.com/locations") } func parse(data: Data?) { if let d = data { let jsonDecoder = JSONDecoder() do { let stuff = try jsonDecoder.decode(Array<Location>.self, from: d) self.dummyData = stuff DispatchQueue.main.async { self.tableView.reloadData() } } catch let error { print(error) } } } func fetch(url: String) { let myURL = URL(string: url)! let httpTask = URLSession.shared.dataTask(with: myURL) { (optionalData, response, error) in self.parse(data: optionalData) } httpTask.resume() } }