iOS Development - Advanced iOS
iOS Development with Swift - Advanced iOS Topics
Section titled “iOS Development with Swift - Advanced iOS Topics”This chapter delves into more advanced iOS features crucial for building feature-rich applications. We’ll cover creating multi-screen apps, navigation, displaying lists of data with Table Views, data persistence, and basic web content display.
Carefully review each section, as these concepts form the backbone of many iOS applications.
Multiple View Controllers and Navigation
Section titled “Multiple View Controllers and Navigation”Most apps consist of multiple screens. Each screen is typically managed by a UIViewController. We’ll learn how to add multiple view controllers and navigate between them.
Start with a new project or an existing one. By default, a new project (using Storyboard) comes with one UIViewController scene in Main.storyboard and its corresponding ViewController.swift file. The arrow pointing to this scene in the storyboard indicates it’s the initial view controller (entry point).
Adding a Second View Controller
Section titled “Adding a Second View Controller”- Open
Main.storyboard. - Open the Object Library (Shift-Command-L).
- Search for ‘View Controller’ and drag a new View Controller object onto the storyboard canvas, placing it beside the existing one.
- Create a new Swift file for this view controller: Right-click on your project’s folder in the Project Navigator > New File… > Choose ‘Cocoa Touch Class’ > Next. Name it (e.g.,
SecondViewController), make it a subclass ofUIViewController, ensure ‘Language’ is Swift, and ‘Also create XIB file’ is unchecked. Click Next, then Create. - Link the storyboard scene to this new class: Select the new View Controller scene in the storyboard. Go to the Identity Inspector (third icon from the right in the Inspector pane). In the ‘Class’ field, type and select
SecondViewController.
You now have a second view controller scene and its Swift class, but no way to reach it yet.
Adding Navigation to the Application
Section titled “Adding Navigation to the Application”Transitioning from one view controller to another is typically done using ‘Segues’ in Storyboard, often managed by a UINavigationController.
Using a Navigation Controller and Segues
Section titled “Using a Navigation Controller and Segues”- Embed the first View Controller in a Navigation Controller: Select your initial View Controller scene in
Main.storyboard. Go to the Xcode menu: Editor > Embed In > Navigation Controller. This will add a Navigation Controller scene before your first VC and add a navigation bar to your first VC. - Add a button to the first View Controller: Drag a
UIButtononto your first View Controller’s view. Title it ‘Go to Second View’. - Create a Segue: Control-drag from this button to the
SecondViewControllerscene. A popup will appear. Under ‘Action Segue’, choose ‘Show’. This creates a segue that will push the second view controller onto the navigation stack when the button is tapped.
Run the app. You’ll see the first view controller with a navigation bar. Tapping the button will transition to the second view controller, and the navigation bar will automatically show a ‘Back’ button to return to the first view.
Customizing the Navigation Bar
Section titled “Customizing the Navigation Bar”Select the Navigation Item of a view controller (it appears inside the navigation bar in the storyboard document outline or by clicking the bar area within the VC scene). In the Attributes Inspector, you can set:
- Title: The title displayed in the center of the navigation bar for that view controller.
- Prompt: A smaller line of text above the title.
- Back Button: Customize the text of the back button that appears on the next screen when this VC is pushed.
You can also add UIBarButtonItems to the navigation bar (e.g., for ‘Edit’, ‘Add’, ‘Done’ actions). Drag a ‘Bar Button Item’ from the Object Library onto the navigation bar in your storyboard. To make it trigger a segue, control-drag from the bar button item to another view controller and choose ‘Show’.
Table Views (UITableView)
Section titled “Table Views (UITableView)”UITableView is used to display data in a single-column list of rows, which can be grouped into sections. It’s highly efficient for displaying large datasets.
Let’s create a simple table view. Create a new project or use an existing View Controller.
Setting up the Table View in Storyboard
Section titled “Setting up the Table View in Storyboard”- Drag a ‘Table View’ from the Object Library onto your View Controller’s view. Use Auto Layout to make it fill the view.
- Prototype Cells: Select the Table View. In the Attributes Inspector, set ‘Prototype Cells’ to 1. This adds a template cell to the table view in the storyboard.
- Cell Identifier: Select the ‘Table View Cell’ (either directly or via the Document Outline). In its Attributes Inspector, set the ‘Identifier’ to a unique string, e.g., ‘MyCell’. This identifier is used to dequeue cells in code.
- Style (Optional): You can change the cell’s ‘Style’ (e.g., Basic, Right Detail, Subtitle) in the Attributes Inspector to get pre-configured label layouts.
Connecting Data Source and Delegate
Section titled “Connecting Data Source and Delegate”A table view needs a data source (to provide data) and a delegate (to handle interactions and appearance).
- In Storyboard: Control-drag from the Table View to the View Controller icon (yellow circle) at the top of its scene. Select ‘dataSource’. Repeat and select ‘delegate’.
- In
ViewController.swift: Conform to the protocols:class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { ... } - Add an outlet for the table view: Control-drag from the Table View in Storyboard to your
ViewController.swiftfile and create an outlet namedtableView.
Implement the required UITableViewDataSource methods:
// Sample data arraylet dataArray = ["Apple", "Banana", "Cherry", "Date", "Elderberry"]
// MARK: - UITableViewDataSourcefunc tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dataArray.count // Number of rows}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // Dequeue a reusable cell let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath)
// Configure the cell let item = dataArray[indexPath.row] cell.textLabel?.text = item // Works if cell style is Basic, Left Detail, etc.
return cell}In viewDidLoad(), ensure the delegate and dataSource are set if not done in storyboard, or if you prefer to set them in code:
tableView.dataSource = self
tableView.delegate = self
Also, you might want to add tableView.reloadData() if your data changes after viewDidLoad.
Run the app. You should see a list of fruits. For more complex cells, you’d create a custom UITableViewCell subclass.
Example: Egg Timer Application (Using Timer)
Section titled “Example: Egg Timer Application (Using Timer)”This application demonstrates using Timer to create a countdown. The UI will have a label to display time, and buttons for Start, Pause, Reset, and adjusting time.
UI Setup (Conceptual)
Section titled “UI Setup (Conceptual)”- Navigation Bar: Title ‘Egg Timer’. Left Bar Button Item ‘Pause’ (e.g., system pause icon). Right Bar Button Item ‘Start’ (e.g., system play icon).
- Center Label: Large label to display remaining time (e.g.,
timeLeftLabel). Initially ‘210’. - Toolbar (at bottom):
- Bar Button Item ‘-10s’.
- Flexible Space Bar Button Item.
- Bar Button Item ‘Reset’.
- Flexible Space Bar Button Item.
- Bar Button Item ‘+10s’.
Connect these UI elements (label as outlet, buttons as actions) to your
ViewController.swift.
Core Logic (Swift Code)
Section titled “Core Logic (Swift Code)”import UIKit
class EggTimerViewController: UIViewController {
@IBOutlet weak var timeLeftLabel: UILabel! // Add outlets for buttons if their state needs to change (e.g., enable/disable)
var eggTimer: Timer? // Use optional Timer var timerActive = false var timeLeft = 210 { // Initial time in seconds didSet { timeLeftLabel.text = String(timeLeft) } }
override func viewDidLoad() { super.viewDidLoad() timeLeftLabel.text = String(timeLeft) // Setup initial button states if needed }
func processTimer() { if timeLeft <= 0 { stopTimer(reset: false) // Stop but don't reset automatically timeLeftLabel.text = "Done!" // Optionally play a sound or show an alert return } timeLeft -= 1 }
func startTimer() { if !timerActive { timerActive = true // Invalidate existing timer before starting a new one eggTimer?.invalidate() eggTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in self?.processTimer() } // Update button states (e.g., disable Start, enable Pause) } }
func stopTimer(reset: Bool) { timerActive = false eggTimer?.invalidate() eggTimer = nil if reset { timeLeft = 210 } // Update button states (e.g., enable Start, disable Pause) }
// MARK: - IBActions @IBAction func startTimerButtonTapped(_ sender: Any) { startTimer() }
@IBAction func pauseTimerButtonTapped(_ sender: Any) { stopTimer(reset: false) }
@IBAction func subtractTimeTapped(_ sender: Any) { if timeLeft > 10 { timeLeft -= 10 } else { timeLeft = 0 // Prevent going below zero } if timeLeft == 0 && timerActive { // If timer was active and reached 0 processTimer() // To trigger "Done!" state } }
@IBAction func resetTimerTapped(_ sender: Any) { stopTimer(reset: true) }
@IBAction func addTimeTapped(_ sender: Any) { timeLeft += 10 }}The Timer.scheduledTimer method now often uses a block-based approach for its action, which can capture self weakly to avoid retain cycles. The #selector approach is also still valid but requires an @objc method.
Storing Data on Local Storage
Section titled “Storing Data on Local Storage”Apps often need to save data locally on the device. Two common ways are UserDefaults (for small pieces of data like settings) and Core Data (for larger, structured datasets).
UserDefaults
Section titled “UserDefaults”UserDefaults is a simple key-value store, ideal for user preferences or simple app state.
// Get the standard UserDefaults instancelet defaults = UserDefaults.standard
// Set valuesdefaults.set("John Doe", forKey: "userName")defaults.set(25, forKey: "userAge")defaults.set(true, forKey: "isUserLoggedIn")
// Get values (provide default if key doesn't exist or type mismatch)let userName = defaults.string(forKey: "userName") ?? "Guest"let userAge = defaults.integer(forKey: "userAge") // Defaults to 0 if not foundlet isLoggedIn = defaults.bool(forKey: "isUserLoggedIn") // Defaults to false
// To save immediately (optional, system does it periodically)// defaults.synchronize() // Less needed in modern iOS, but can be used.Core Data
Section titled “Core Data”Core Data is a powerful object graph and persistence framework. It allows you to model, save, and retrieve structured data.
- Project Setup: When creating a new project, check the ‘Use Core Data’ box. This adds a
.xcdatamodeldfile and boilerplate code inAppDelegate.swiftfor the Core Data stack (NSPersistentContainer). - Data Model: Open the
.xcdatamodeldfile. Click ‘Add Entity’ to create a table (e.g., ‘User’). Select the entity and add ‘Attributes’ (columns, e.g.,nameof type String,ageof type Integer 16/32/64). - Accessing the Context: The
NSPersistentContainerinAppDelegateprovides theviewContext, which is your scratchpad for working with managed objects.
import UIKitimport CoreData
// Assuming 'User' entity with 'name' (String) and 'creationDate' (Date) attributes
// Get the AppDelegate and NSManagedObjectContextguard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { // Handle error appropriately return}let context = appDelegate.persistentContainer.viewContext
// Saving Datafunc saveNewUser(name: String) { // Create a new User managed object // If you've generated NSManagedObject subclasses (Editor > Create NSManagedObject Subclass...): // let newUser = User(context: context) // newUser.name = name // newUser.creationDate = Date()
// Or using NSEntityDescription if no subclass generated: guard let userEntity = NSEntityDescription.entity(forEntityName: "User", in: context) else { print("Failed to find User entity") return } let newUser = NSManagedObject(entity: userEntity, insertInto: context) newUser.setValue(name, forKey: "name") newUser.setValue(Date(), forKey: "creationDate")
do { try context.save() // Save changes to persistent store print("User '\(name)' saved.") } catch let error as NSError { print("Could not save. \(error), \(error.userInfo)") }}
// Fetching Datafunc fetchUsers() -> [NSManagedObject] { // Or [User] if using subclasses // Create a fetch request let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "User") // For subclasses: let fetchRequest: NSFetchRequest<User> = User.fetchRequest()
// Optional: Add sort descriptors or predicates (filters) // let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) // fetchRequest.sortDescriptors = [sortDescriptor] // let predicate = NSPredicate(format: "name CONTAINS[c] %@", "John") // fetchRequest.predicate = predicate
do { let users = try context.fetch(fetchRequest) for user in users { if let userName = user.value(forKey: "name") as? String { print("Fetched user: \(userName)") } } return users } catch let error as NSError { print("Could not fetch. \(error), \(error.userInfo)") return [] }}
// Example Usage (e.g., in viewDidLoad or an IBAction)// saveNewUser(name: "Alice")// let allUsers = fetchUsers()Core Data involves more setup but is very powerful for complex data models and large datasets. The do-catch blocks are essential for error handling.
Controlling the Keyboard
Section titled “Controlling the Keyboard”Managing the on-screen keyboard is important for a good user experience.
Dismiss Keyboard on Tap Outside Text Field
Section titled “Dismiss Keyboard on Tap Outside Text Field”Add this to your UIViewController subclass:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { self.view.endEditing(true) // Dismisses keyboard for any active text field in the view}Dismiss Keyboard on Return Key Tap
Section titled “Dismiss Keyboard on Return Key Tap”- Make your View Controller conform to
UITextFieldDelegate:class ViewController: UIViewController, UITextFieldDelegate { ... } - In
viewDidLoad(), set the delegate for your text field(s):myTextField.delegate = self(wheremyTextFieldis an outlet to yourUITextField) - Implement the delegate method:
func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() // Dismisses the keyboard for this specific text field return true // Indicates the return key press was handled}Displaying Web Content (WKWebView)
Section titled “Displaying Web Content (WKWebView)”To display web content within your app, use WKWebView (from the WebKit framework). UIWebView is deprecated.
- Add
WKWebViewto your View: In Storyboard, search for ‘Web Kit View’ in the Object Library and drag it onto your view. Constrain it as needed. Create an outlet for it (e.g.,webView). - Import WebKit:
import WebKitin yourViewController.swift. - Load a URL:
import UIKitimport WebKit
class WebViewController: UIViewController, WKNavigationDelegate {
@IBOutlet weak var webView: WKWebView!
override func viewDidLoad() { super.viewDidLoad()
webView.navigationDelegate = self // Optional, for handling navigation events
guard let url = URL(string: "https://www.apple.com") else { return } let request = URLRequest(url: url) webView.load(request) }
// Example WKNavigationDelegate methods (optional) func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { print("Started to load") }
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { print("Finished loading") }
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { print("Failed to load: \(error.localizedDescription)") }}Remember App Transport Security (ATS): If loading non-HTTPS URLs, you’ll need to configure exceptions in your Info.plist (e.g., NSAllowsArbitraryLoads or domain-specific exceptions), though HTTPS is strongly preferred.
These advanced topics provide a strong foundation for building complex and interactive iOS applications. Continue exploring Apple’s documentation and experimenting with these features.