iOS Development

Overview

This document relates to developing iOS applications on macOS Sierra versions 10.12.6 to 10.13.3 using Xcode versions 9.0.1 to 9.2 and iOS 10.3.1 together with Swift 4.

Getting Started

Xcode

Xcode Concepts

Also, select Help > Xcode help from the Xcode menu, then Xcode overview.

To show line number and a page guide, go to Xcode > Preferences > Text Editing. Select Line numbers and Page guide at column:.

Installing Simulators

To download and install older iOS simulators:

Xcode -> Preferences -> Components -> Simulators

https://stackoverflow.com/questions/4262018/xcode-simulator-how-to-run-older-ios-version

See also:

Downloading Simulator Runtimes

Simulator runtimes are downloaded using Xcode, under Preferences -> Components.

They are saved under /Library/Developer/CoreSimulator/Profiles/Runtimes/. Restart Xcode after manually removing old simulator runtimes from that folder.

Creating Simulators From The Command Line

$ xcrun simctl help create
$ xcrun simctl list devicetypes runtimes
$ xcrun simctl create 'iPhone 12 Pro Max (iOS 14.4)' 'iPhone 12 Pro Max' iOS14.4
$ xcrun simctl create 'Apple Watch Series 6 - 44mm (watchOS 7.2)' 'Apple Watch Series 6 - 44mm' watchOS7.2
$ xcrun simctl create "Apple Watch Series 9 (45mm)" \
"com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-9-45mm" \
"com.apple.CoreSimulator.SimRuntime.watchOS-10-4"
$ xcrun simctl list devices
$ xcrun simctl help pair
$ xcrun simctl pair ${WATCH_UUID} ${PHONE_UUID}
$ xcrun simctl list devices pairs

See iOS Simulator from the Command Line for more information.

Removing Redundant Simulators

For help on using the simctl command:

$ xcrun simctl help

For help on the delete subcommand:

$ xcrun simctl help delete

To delete all unavailable devices:

$ xcrun simctl delete unavailable

Initial Learning

  1. Read The Swift Programming Language (Swift 4) also available as a free iBook from the App Store.

  2. Follow the Start Developing iOS Apps (Swift) tutorial

  3. Read the View Controller Programming Guide for iOS

  4. Review the documents listed under the What's next? Where to Go from Here section of the Start Developing iOS Apps (Swift) tutorial

Debugging and Logging

See WWDC 2020—Explore logging in Swift

Since macOS 11.0, iOS 14.0 and watchOS 7.0 Logger statements can use String interpolation, including formatting arguments, e.g.

mylogger.log("\(myItem, align: .left(columns: 25)) \(myValue, format: .fixed(precision: 2))")

Other options for align and format can be found when using Xcode by using code completion. See also Generating Log Messages from Your Code | Apple Developer Documentation.

Values can be masked by using hashes, allowing comparisson of values without obvious exposure in the log. E.g.

mylogger.log("My secret is: \(mySecret, privacy: .private(mask: .hash))")

Logs can be collected on macOS with a connected device, e.g.

$ man log
$ log collect --device --start '2021-03-31 10:22:00' --output my.logarchive

Logs can be opened in the Console application. However, it appears logging is normally disabled for the Apple Watch. You now need to install a profile which enables logging for a few days. It's still very flaky to use.

Log Levels

  • Debug—Useful only for debugging—Not persisted
  • Info—Helpful for troubleshooting—Persisted only during log collect
  • Notice (Default)—Essential for troubleshooting—Persisted
  • Error—Execution error—Persisted
  • Fault—Program bug—Persisted

Persisted log levels are only persisted within a storage limit for the device. Faults are potentially retained longer than errors, and errors longer than notices.

See also:

See also callStackSymbols() which provides an array of strings showing the state of the call stack, which could be dumped to a debug log etc.

Table Views

TableViews are quite useful for many if not most uses. Note that a table view has content which consists of either Static Cells or Dynamic Prototypes. The Start Developing iOS Apps (Swift) tutorial shows how to use Dynamic Prototypes, but if you know the content of the table view at compile time, it is quite straight-forward to implement in the storyboard. Briefly;

  1. Drag a Table View Controller onto the storyboard from the Object Library.

  2. Use the Attributes Inspector to change the Table View Content to Static Cells.

  3. Consider changing the Style to Grouped

  4. Set the number of rows for the initial section and add elements to match the behviour of the majority of the sections. These wil be duplicated when you create more sections.

  5. Change the number of sections to create the additional sections.

  6. Create a UITableViewController class and assign it to the Custom Class property of the table view in the Identity Inspector.

See also:

Disabling Cell Selection in Dynamic Table

To disable cells being highlighted when selected in a dynamic table view, use the storyboard to select the prototype cell in the Document Outline. It will be a child of the Table View. In the Attributes Inspector, under the Table View Cell properties, beneath the Identifier section, change the Selection option to None.

Returning Data to a Presenting View Controller

Implement the delegate pattern. See the Dismissing a Presented View Controller in the Presenting a View Controller section of the Presentations and Transitions chapter of the View Controller Programming Guide for iOS

  1. Create a simple protocol defining a suitable method to pass the data.

  2. The presented view has an optional delegate property of that protocol type.

  3. The presenting view implements the delegate protocol. It sets the presented view delegate to itself before the view is presented, perhaps in the prepare(for:sender:) method of the presenting view, which will be called when the segue is activated.

  4. When values change on the presented view, it can make appropriate calls to the delegate method(s) to update the presenting view's data model.

The back button of a pushed view is actually defined in the presenting parent. The logic seems to be that the pushed view's back is logically the pushing view.

If the presenting view does not have a navigation item, the back button text of the called view will be Back. Add a navigation item by dragging a Navigation Item from the object library to beneath the controller element of scene in the outline view.

Then select the new navigation item in the outline view and set the title for the presenting page. If you want presented pages to show a different text for the back button, enter the values in the navigation item's back button setting in the attributes inspector.

The text for the back button of the pushed view should change appropriately in the interface builder to display either the title of the presenting view or if the back button text has been defined, it'll use that. Note that at run-time, if the text to be displayed for the back button is too long for the device screen size, it is replaced with the localised version of Back.

See also:

Split View Controllers and Navigation

In a split view, in some situations, a detail view navigation controller uses the title of the master navigation controller to name the left back button of the detail view navigation bar. One such scenario is in portrait mode on an iPad Pro (12.9-inch). The title can be set in the storyboard.

To display the left back button on the detail view, in the storyboard, select the Navigation Item in the detail view controller and use the Attributes inspector to set the Left Items Supplement checkbox. Alternatively, set it at runtime with code similar to myViewController.navigationItem.leftItemsSupplementBackButton = true.

How to manually rename view controller

The easiest method is to right click the classname in the class definition file and choose the refactor method. But if you want or need to fix things manually, follow these steps:

  1. Rename it in the project navigator by selecting ViewController.swift and hitting return.

  2. Rename the class and also the header comment

  3. Open Main.storyboard

  4. Select the View Controller by clicking on its scene dock or select the View Controller in the outline view

  5. Open the Object Inspector (Option-Command 3)

  6. Under Custom Class select the renamed Class

Add storyboard controls to stack view

  1. Shift-click each of the controls to select them

  2. Select Editor > Embed In > Stack View

Toolbar

  1. Open the storyboard and select the related navigation controller

  2. In the Attributes inspector, Navigation Controller section, select Shows Toolbar

  3. Within a view controller that displays the toolbar, programmatically create instances of UIBarButtonItem

  4. Assign an array containing the buttons to the view controller's toolbarItems property

  5. In the storyboard, select each view controller that you do not wish to display the toolbar on and select the Hide Bottom Bar on Push in the Attributes inspector

  6. How to show and hide a toolbar inside a UINavigationController

Button Border Colours

https://stackoverflow.com/questions/14792238/uiviews-border-color-in-interface-builder-doesnt-work/27986696#27986696

Keyboard Navigation with the Next Button

Making the next button move to the next edit field involves giving each UITextField a integer tag, each tag value incrementing from the previous. In the controller handle when the return (next/send/done/etc) key is pressed, get the tag value of the current UITextField, increment it, and find the associated UITextField from the owning super view, (most likely the view of the current controller) and set it to become the first responder.

Your view controller implements the UITextFieldDelegate and defines the textFieldShouldReturn(textField:) method.

The tags can be set for the text fields in the interface builder. The Connections Inspector is used to associate the delegate with the view controller which implements the UITextViewDelegate.

Home Screen Quick Actions

Long Press Gesture Handling

Simulator

Local files are stored in a directory under ~/Library/Developer/CoreSimulator/Devices. To find out the id of the device, open the Devices and Simulators window in Xcode. Window > Devices and Simulators Select either Simulators or Devices as appropriate, then select the device. The Identifier is displayed under the details of the device at the top of the window.

iCloud in Simulator

Resync simulator with iCloud by selecting Debug > Trigger iCloud Sync from simulator menu.

Type X does not conform to protocol NSObjectProtocol

Implement NSObject protocol.

https://stackoverflow.com/questions/40705591/class-does-not-conform-nsobjectprotocol

Launch URL in Safari

Location Manager

See also:

Desired Accuracy

The following observations have been noted in testing on iPhone X with iOS 11:

  • 10 metres - seems to turn on GPS and cause frequent calls to the delegate. Additionally, provides speed and bearing information.
  • 100 metres - infrequent calls to the delegate and doesn't seem to use GPS. Locations are frequently less accurate than 100 metres to the point where obtaining locations with an HDOP of less than 100 metres is unlikely.
  • Setting the distance filter causes the OS not to call the location manager until the distance criteria has been met. This is likely to be an important value in reducing battery usage.

See also:

Deferred Locations

Note: I have been unable to get deferred locations working with iOS 11 on iPhone SE nor on iPhone X.

Simulating Locations

The Xcode Debug has a Simulate Location option, which is disabled unless the app is running in a simulator with the debugger attached to the running app process. However, it seems to be broken in Xcode 9.2. At best it will provide one point per LocationManager session then stop.

However, in the simulator menu Debug -> Location there are options to choose between a City Run, City Bicycle Ride or Freeway Drive that simulate real trips. Note: This is in the simulator application, not Xcode.

Internationalisation/Localisation

In Xcode 15 and later, localisation uses Xcode Localization Catalogs instead of XLIFF files. Existing projects can be migrated to use String Catalogs by right-clicking a localised file and choose the migrate option.

See the following in Xcode help for the full documentation:

For older versions of Xcode, see:

Xcode's XLIFF based translations can be managed using OmegaT. See OmegaT.html for some notes on its use.

Attributed Strings

Share Sheet (UIActivityViewController)

Document Handling - Uniform Type Identifiers (UTI)

Editing Documents Inplace

Core Data and CloudKit

Watch WWDC 2017 - What's New in Core Data. Covers topics such as query optimisation with indexes and persistent history tracking. That latter is probably the most effective technique to use for updating offline changes etc. to the cloud.

To handle when records have been deleted, you probably need to preserve the primary key value in order to delete the same record on the Apple Watch. You do this by defining the relevant attribute as one to Preserve After Deletion.

In the Xcode model editor, Select the attribute and open the Data Model inspector panel. In the Advanced sub-section of the Attribute section, there is the option to Preserve After Deletion.

Core Data and Concurrency

To run a debug session with a device and check CoreData Concurrency...

  1. Product > Scheme > Manage Schemes....

  2. Select a scheme in the list, then click Edit....

  3. Select Run Debug in the left sidebar.

  4. Select the Arguments tab.

  5. Under Arguments Passed On Launch add:

    -com.apple.CoreData.ConcurrencyDebug 1
    
  6. https://oleb.net/blog/2014/06/core-data-concurrency-debugging/

  7. https://stackoverflow.com/questions/31391838/making-com-apple-coredata-concurrencydebug-1-work
  8. https://stackoverflow.com/questions/36465603/ios-core-data-dispatch-async-background-queuing-crash
  9. https://developer.apple.com/library/archive/releasenotes/General/WhatNewCoreData2016/ReleaseNotes.html

Running Debug Builds with Production CloudKit

Add the following key to your entitlements file as a Key of type String and a of value Development or Production.

com.apple.developer.icloud-container-environment

The entitlements file can be found within the project hierarchy. It should be named $PROJECT_NAME.entitlements. It can be opened in Xcode using Finder.

iCloud File Management

Fonts

Artwork

Install the ImageMagick package from MacPorts. See InstallingMacPorts.

Artwork can then be created from a larger original (e.g. 2048x2048) with:

$ convert $FILENAME -resize '40x40!' converted_file.png

To create a scalable PDF image:

$ convert $FILENAME -resize '40x40!' convert_file.pdf

See also:

Generating App Icons

Miscellaneous

Distribution

Free Developer Account

Note: apps distributed using ad hoc distribution with a free developer account will not run on a device after the provisioning profile expires. The certificate for the provisioning profile expires after a maximum of seven days.

Provisioning Profiles

Ad Hoc Distribution

Ad hoc distribution allows you to distribute and install builds on internal test devices. The device IDs (UDID) need to be included in your developer account and your provisioning profile certificate needs to include these device IDs, otherwise, they either fail to install, or simply fail/crash on startup.

There are instructions in Xcode Help under Xcode Help > Archive, distribute, and test > Test a beta version > Distribute to registered devices (iOS, tvOS, watchOS).

Create the build in Xcode 9.2 by selecting the build target as Generic iOS Device in Xcode's toolbar. Then select Product > Archive from the main menu. This will build the app, then display the Organizer window containing this and previous builds. You can display this window in the future by selecting Window > Organizer from the main menu.

Xcode 10 introduces a new build system. It can be changed by selecting your project, then File > Project settings from the main menu. Then under Shared Project Settings:, Build System: you can switch to Legacy Build System.

To export the build for distribution:

  1. Click the Distribute App button (previously named Export...) in the Organizer window.

  2. Select a method of distribution.

  3. Choose an appropriate value for the App Thinning option, None creates a single universal app that runs on all supported devices. Note that in Xcode 10.1, thinned apps won't install via ad hoc distribution, so leave this option as None in that situation.

  4. Uncheck Rebuild from Bitcode - there doesn't seem to be a way to analyse crash logs when apps are distributed with this option set.

  5. Optionally, choose to Strip Swift symbols

  6. If you want to distribute the app so it can be installed by users using Safari, choose the Include manifest for over-the-air installation

  7. Click Next.

  8. If you chose to create a manifest, fill in the Distribution manifest information. This is information is not remembered by Xcode, so store the details somewhere else for future reuse.

  9. Click Next.

  10. Choose the Automatically manage signing option. This should work in most cases. Xcode 10 doesn't seem to create a new ad hoc provisioning profile after an old one expires, so it is necessary to create one within iTunes Connect, download and import it into Xcode. Also, when you start using a new device for internal testing, you will need to update the certificates within iTunes Connect to include the new device and manage signing manually. You need to create an ad hoc provisioning profile that includes the devices registered for ad hoc distribution.

  11. Click Next. The artefacts are created and signed.

  12. Click Export and choose a target location for the exported files to be saved.

  13. If distributing for installation via Safari, upload manifest.plist and all the .ipa archives to a single directory on your website. Create an HTML page containing an href link of the following format:

    <a href="itms-services://?action=download-manifest&url=https://HOST/PATH_TO_FOLDER/manifest.plist">Click here to install</a>
    

If installation fails, there is unlikely to be an error message to analyse. When you click the link, you should immediately see a dialog asking if you would like to install the app. If it fails before that point, it may be because the app is already installed and was installed from another source, most likely the official app store. Either install using Xcode, or uninstall the old version first.

The website's log should see some GET requests. One should be for the manifest.plist file. That GET request should show the model that it looks up in the manifest file, e.g. iPhone10,6. The next GET request should be for the IPA file listed for that model in the manifest.plist file.

If the install starts but fails after it appears to have downloaded the IPA file, it is most likely a problem with the signing certificate, or the device not having been registered in the developer account. See Register a single device in the Developer Account Help, under Register Devices > Register a single Device.

You may also need to 'reset' your registered devices after commencing a new developer membership year, otherwise the ad hoc app will fail to properly install on the device.

  1. To install using Xcode;

    1. connect your device to the build machine, e.g. via a lightning cable

    2. Open the Devices and Simulators window (Window > Devices and Simulators from the main menu)

    3. Select the Devices tab

    4. Select the device under Connected in the left sidebar

    5. Click the + symbol underneath the list of INSTALLED APPS

    6. Browse to and select the relevant .ipa archive file

    7. The application installs on the target device

See also:

Beta Testing with TestFlight

Sandbox Testing

You need unique valid e-mail addresses for each sandbox tester. Apparently, the sandbox account cannot be used in the simulator environment.

Warning—Logging into a production version of an Apple environment causes the sandbox account to become invalid and cannot be used again.

App Store Release Process

Crash Logs

See Technical Note TN2151: Understanding and Analyzing Application Crash Reports for full details.

As a generalisation, if you want to symbolicate a crash report from a device connected to your machine running Xcode and the app has been installed via the app store, you need to download the dSYM files that were generated during compilation on the App Store. The App Store build with have a different UUID to the build in the Xcode archive.

  1. In Xcode (version 11.3), display the Organizer, Window > Organizer, and select the Archives tab.

  2. Select the appropriate app in the iOS App sidebar.

  3. Select the appropriate build that was uploaded to the App Store.

  4. In the right sidebar, click the Download Debug Symbols button.

  5. Open the Devices and Simulators window, Window > Devices and Simulators.

  6. Select the device in the left sidebar.

  7. Click the View Device Logs button.

  8. Select and right-click the appropriate crash report in the left sidebar.

  9. Select the Re-Symbolicate Log option.

  10. Right-click the crash report and select the Export Log option, saving it somewhere.

  11. Open the saved report in Xcode, File Open... and associate it with the appropriate project.

  12. The crash report should then be displayed in the project.

  13. Diagnosing issues using crash reports and device logs | Apple Developer Documentation

  14. Adding identifiable symbol names to a crash report | Apple Developer Documentation
  15. Understanding the exception types in a crash report | Apple Developer Documentation
  16. Technical Q&A QA1747 - Debugging Deployed iOS Apps
  17. Retrieve dSYMs for Bitcode apps
  18. ios - How is a .dSYM file created? - Stack Overflow
  19. iphone - dSYM file from device - Stack Overflow
  20. iphone - Can I still symbolicate a distribution build that stripped its debug symbols? - Stack Overflow
  21. Getting dSYM's from Enterprise App | Apple Developer Forums
  22. Demystifying iOS Application Crash Logs

U.S. Export Regulations

Screen Recording

Identifying iPad Models

Root Certificates

Miscellaneous

Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a tableview cell's content view. We're considering the collapse unintentional and using standard height instead.

In the storyboard, enter a rowHeight of 44 for the table view, instead of leaving it at automatic.

Excluding Files from Backup

Guides

Force reboot iPhone X

  1. Press and quickly release volume up button

  2. Press and quickly release volume down button

  3. Press and hold the side/power button until the Apple logo is displayed. Can take 10 seconds or longer.

https://support.apple.com/en-gb/HT201412

Compiler Error Messages

Type 'MyClass' does not conform to protocol 'NSObjectProtocol'

Make MyClass extend NSObject and all the methods required by NSObjectProtocol are provided with a default implementation.

Useful Commands

The following sed command run on macOS will comment out all os_log statements with a type of .debug.

$ sed -i '~' -E 's/(^[[:space:]]*)(os_log\(.*[[:space:]]+type:[[:space:]]+\.debug)/\1\/\/ \2/g' *.swift

Other information

Xcode

Swift Language Version

$ xcrun swift --version

Check the path to ensure it is the intended version of Xcode that xcrun is executing:

$ xcrun --find swift

The version can be changed in the Xcode project settings, under Build Settings > Swift Compiler - Language > Swift Language Version.

See https://stackoverflow.com/questions/30790188/how-do-i-see-which-version-of-swift-im-using

Resources


-- Frank Dean - 23 Dec 2017

Related Topics: Internationalisation, iOSTips, MacOSXTips, OmegaT.html, Swift.md, ViewRangerTips, watchOSDevelopment, Xcode