MacKuba

Kuba Suder's blog on Mac & iOS development

UIKit

App ClipsAppKitExtensionsFoundationLocationLoggingMacMapsPerformancePhotosPrivacySafariSwiftSwiftUIUIKitWatchKitWWDC 14WWDC 16WWDC 18WWDC 19WWDC 20

View as index

iPad and iPhone apps on Apple Silicon Macs

Categories: Mac, UIKit, WWDC 20 0 comments Watch the video

iPhone and iPad apps can now run unmodified on macOS on ARM, the same exact binary as on iOS

Uses Catalyst APIs, so apps get a lot of behavior for free

Making a separate Catalyst build manually is still recommended for optimal result (and supports Intel Macs)

To run on the Mac, an iOS app must not:

  • depend on missing symbols, frameworks or other functionality (Xcode should help you at least in some cases)
  • depend on hardware which Macs don’t have

Compatible apps are automatically made available for Macs unless you opt out

Possible compatibility issues:

Hardware:

  • Macs use mouse & keyboard and not touch screens, so if an app uses touch in non-standard ways or relies on multi-touch, this might not work
  • apps that depend on sensors like accelerometer, gyroscope, compass, depth camera, precise GPS will not work
  • iOS apps expect one front and one back camera, Macs may have one, none or multiple (use AVCaptureDeviceDiscoverySession)

UI:

  • alerts and panels like open/save will look differently than on iOS, so don't assume their dimensions and position
  • if an iPad app supports multitasking, it will be fully resizable to any dimensions, including some layouts and sizes you might not expect
  • iPhone-only apps and iPad apps that don’t support multitasking will run in a fixed size window matching iOS device size
  • window resizing is live on macOS – make sure it’s optimized

System software:

  • on the Mac, user can move the app bundle to different locations, application container is also in a different location
  • APIs returning device type or model will include Mac model types
  • screen size will not be one of the standard iOS device screen sizes that you may expect

You can debug, profile and test iOS on an ARM Mac as you would expect

Distribution for Mac devices works the same as for iOS devices

Apps can reuse existing subscriptions and in-app purchases

App thinning automatically selects most appropriate resources for the given Mac

→ when making a thinned build, you can select the option to optimize for any Mac

TestFlight is (still) not available on the Mac

Design with iOS pickers, menus and actions

Categories: UIKit, WWDC 20 0 comments Watch the video

iOS now includes new lightweight menus in a lot of places, same as the context menus shown below previews

They replace a lot of previous use of action sheets

Disadvantages of action sheets:

  • dimming the background of the whole window is a too strong effect in most cases
  • wastes space because the rows take full screen width even though labels are usually short
  • they always appear at the bottom, so you often need to move your finger across whole screen if the element that launched the menu is at the top

The new menus appear next to the element they launch from, take less space and require less finger movement, the transition is shorter and lighter

Actions in the new menus can also be used for selection and navigation

Each action has a title and an optional image (SF symbol or custom)

Menu can have its own title at the top

It can also include separators between items

A menu can be presented from any button, in a toolbar or the main view

Menu is dismissed when the user taps somewhere outside, so an explicit “cancel” action isn’t needed

Example use cases for menus:

Disambiguation:

  • “add” button shows a choice of what kind of item you want to add
  • “insert photo” button shows a choice of where you want to load the photo from
  • “done” button asks how you want to save the finished video

Navigation:

  • “back” button shows a history when you press and hold it

Selection:

  • “sort” button lets you choose the sorting field

Secondary actions:

  • “…” button displays a list of less important actions grouped in a menu – it lets you clear up the rest of the UI which would otherwise be cluttered with buttons and switches
  • however, do not hide primary actions inside a “more” menu

Destructive actions (delete a list) should be marked as such (with red color) and should show a confirmation in a different place than the first button

→ display an action sheet at the bottom of the screen: Are you sure? Delete List / Cancel

Do this also for destructive actions that aren’t launched from a menu (e.g. when clicking a “Cancel” button while composing a message) – don’t use menus for this since it would make it too easy to confirm by accident

Build with iOS pickers, menus and actions

Categories: UIKit, WWDC 20 0 comments Watch the video

Control design updates:

UISlider and UIProgressView: slightly updated design to make it more similar to the Mac

→ design is again slightly more Mac-like when running in a Catalyst environment – some UI customizations in these classes are ignored there

UIActivityIndicatorView: updated design, larger and with fewer lines

UIPickerView: updated design

→ it’s strongly recommended to use menus instead in Catalyst apps

UIPageControl

  • allows an unlimited number of pages
  • you can use custom images for all page dots (preferredIndicatorImage) or for a specific page (setIndicatorImage(…, forPage: i))
  • backgroundStyle = .prominent

Color picker

UIColorPickerViewController – new color picker dialog like on macOS

  • presents as a sheet or popover
  • has a selectedColor property that you can preset and read from
  • allows picking a color using a few different methods
  • colors can be favorited and reused across apps
  • includes an eyedropper that lets you pick a color from the screen below, or another app on iPad
  • on macOS it appears as the standard Mac color picker panel
var colorPicker = UIColorPickerViewController()
colorPicker.supportsAlpha = true
colorPicker.selectedColor = UIColor.blue
self.present(colorPicker, animated: true, completion: nil)

func colorPickerViewControllerDidSelectColor(
  _ viewController: UIColorPickerViewController
) { … }

func colorPickerViewControllerDidFinish(
  _ viewController: UIColorPickerViewController
) { … }

Date picker

UIDatePicker – new compact style, shows two fields that open a popup when tapped

Inline style – shows picker as part of the main view instead of a popup

datePicker.preferredDatePickerStyle = .automatic / .compact / .inline / .wheels

Menus

button.menu, barButtonItem.menu – shows a context menu when that button is tapped

For UIButton: set button.showsMenuAsPrimaryAction = true

For UIBarButtonItem: set menu but don’t provide a primaryAction

Back button now shows a “history” menu when you press and hold it

Looks at: backBarButtonItem.title, backButtonTitle, title

Implemented in UIControl

UIControl.showsMenuAsPrimaryAction

UIControl.contextMenuInteraction, isContextMenuInteractionEnabled

Register for UIControl.Event.menuActionTriggered to observe when menu is opened

UIDeferredMenuElement – asynchronously provides menu items

Shows a standard loading UI (“Loading” + spinner) until menu items are ready

Once loaded, menu items are cached

UIContextMenuInteraction:

  • updateVisibleMenu(_ block: (UIMenu) -> UIMenu) – replaces currently displayed menu
  • menuAppearance  ⭢  .rich = under a preview, .compact = just the menu

Actions

UIAction is used in more places across UIKit

All controls can be constructed using a UIAction now

UIBarButtonItem:

  • new initializers taking primaryAction and/or menu
  • .fixedSpace(width: x) and .flexibleSpace() spacers

UIButton: init(type:primaryAction:), defaults to using action’s title and image on the button

UISegmentedControl: init(frame: actions:) – creates segments from a list of actions, action is automatically called when that segment is selected

Meet the new Photos picker

Categories: Photos, UIKit, WWDC 20 0 comments Watch the video

PHPicker: new system-provided picker screen that gives you access to photos and videos from the user’s photo library

New design and new easy to use API

It’s recommended that you use this picker instead of building your own custom photo selection UI

New version includes:

  • an integrated search
  • easy multi-select
  • zoom gesture

PHPicker is private by default:

  • the picker screen runs out of process and talks to the app via XPC
  • your app has no direct access to the photos library
  • it doesn’t need to get photo library permission (don’t ask for it unless you *really* need it)
  • you only get selected photos and videos in response

Elements of the API:

PHPickerConfiguration – lets you specify limits and filters:

  • selectionLimit – number of items that can be selected (1 by default, 0 = unlimited)
  • filter – e.g. .images or .any(of: [.videos, .livePhotos])

PHPickerViewController – the main VC handling the picker

  • the picker doesn’t dismiss itself automatically, call picker.dismiss(animated:) when you get the response

PHPickerViewControllerDelegate – delegate for the picker

  • picker(_: didFinishPicking results:)

PHPickerResult – an array of these objects is passed to the app in response

  • get itemProvider from the result
  • check itemProvider.canLoadObject(ofClass: UIImage.self)
  • get the image via itemProvider.loadObject(ofClass: UIImage.self) { … }

You can normally extract picked photos from PHPickerResult item providers without touching the PHPhotoLibrary at all, but if you do need to access the photo library anyway, then pass it to PHPickerConfiguration.init and get assetIdentifier references from the picker results

If you use PHPicker with photo library access and you only got limited access to a subset of photos, then:

  • PHPicker will still let the user choose photos from their whole library
  • but the selection you have direct access to will not be extended by what they choose in the picker

The photo library APIs from UIImagePickerController are deprecated

Handle the Limited Photos Library in your app

Categories: Photos, UIKit, WWDC 20 0 comments Watch the video

Current full photo library access: your app has full access to all contents of user’s photo library, can read and write to the photo database

Limited mode: you only have access to “user’s limited library selection” – a kind of filter for the photo library

You can only fetch assets and resources related to what the user has chosen to give you access to

When the user updates the selection, your app is notified so you can update your UI

This affects all apps using photo library, even apps that are already live

When the user is asked for access to photos, they can choose: Allow access to all / Don’t allow / Select photos

Selected photo set can be changed in the Settings

If an app hasn’t made any changes to support this new mode yet, you will be asked if you want to change selection every time the app accesses the photo library after a restart

Users usually have thousands or tens of thousands of photos in their library, possibly from many years, but they almost never want an app to have access to all of them

Other ways to access the library

How can you use the photo library without requesting access to it at all?

If your app only needs to let you choose a photo for an avatar, or to post to a chat  ⭢  use system photo picker (PHPickerViewController)

  • replacement for UIImagePickerController
  • improved with search and multi-select
  • doesn’t require user to grant photo library access

If you only need to save photos and images to the library, you can ask for add-only access

Full access makes sense for apps like photo browsing and editing apps, camera or backup apps

New APIs

When asking for and checking authorization, you need to specify PHAccessLevel: .readWrite / .addOnly

PHPhotoLibrary.authorizationStatus(for: accessLevel)

If the user gave you limited access (only to a subset of photos), then authorizationStatus == .limited

When requesting access, you can just send a request to the photo library when the user performs an action that requires it, and the popup will appear automatically

If you want to request access explicitly without making a request, do:

PHPhotoLibrary.requestAuthorization(for: accessLevel) { … }

Older authorizationStatus() and requestAuthorization(_ handler:) APIs will be deprecated and will just return .authorized

Other APIs should work the same regardless of access, they will just return fewer items if access is limited

A few exceptions:

  • assets created by your app are automatically accessible to your app
  • you can’t create or fetch user’s albums in limited mode
  • no assets to cloud shared assets or albums

If you want to let the user update the selection:

  • call PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: viewController)
  • monitor changes through PHPhotoLibraryChangeObserver
  • you may want to add some new button in the UI that lets the user access this screen

To prevent the selection alert from appearing after a relaunch, add the key PHPhotoLibraryPreventAutomaticLimitedAccessAlert to Info.plist

Steps to take when updating:

  1. 1. Reconsider if your app needs photo library access at all  ⭢  consider using system picker instead, or asking for write-only access
  2. 2. Adopt the new authorization APIs
  3. 3. Add a button which lets the user access the library selection UI if it makes sense
  4. 4. Add the Info.plist key to prevent the alert

What's new in location

Categories: Location, UIKit, WWDC 20 0 comments Watch the video

Location authorization

Question: how much information does this app need to know about me to do its job?

When you think about it, there’s a pretty wide range of apps that don’t actually need to know precisely where you are, just the approximate area

Precise location is definitely needed for e.g. navigation apps, but not necessarily for social media apps

In iOS 14, when the user is asked for location access, the popup that appears shows a map with current position and a “Precise” switch

This is controlled by the user, you just get either more precise or less precise location

CLLocationManager.authorizationStatus is now an instance property, not a class method

New property accuracyAuthorization: CLAccuracyAuthorization == fullAccuracy / reducedAccuracy

locationManager(_: didChangeAuthorization status:) method is deprecated, replaced with: locationManagerDidChangeAuthorization(_:)

  • invoked when either authorization status or accuracy changes
  • inside, you need to check both manager.authorizationStatus and manager.accuracyAuthorization if needed

Location updates in reduced accuracy mode are still delivered to didUpdateLocations method

  • horizontalAccuracy tells you exactly how precise the location is
  • reduced precision location will change around 4 times an hour

If possible, build your app in such a way that it’s possible to use it with reduced accuracy if the user prefers that

Do not treat imprecise location as the true location of the user

Depending on the type of app and what you use the location info for, you may want to display approximate location differently

In Maps, if you only grant approximate location, it shows your location as a large circle + shows a warning bubble at the top “Precise Location: Off”

Asking for full precision authorization

If you have a feature that requires full accuracy, you can ask the user to temporarily grant you full location precision:

manager.requestTemporaryFullAccuracyAuthorization(withPurposeKey: “WantsToNavigate”) { … }

You have to explain the user why exactly you need precise location in this specific case (there might be different reasons), so you need to pass a specific purpose key

Purpose keys are defined in Info.plist, under NSLocationTemporaryUsageDescriptionDictionary

Temporary precise location authorization works more or less like “Allow Once”, persists until the app is relaunched

Reduced accuracy should not affect apps using the “Significant location changes” API

For apps using the “Visits” API you will still get notifications at the exact time when the user enters/leaves an area

Beacons and other region monitoring APIs are disabled in reduced accuracy

If you only need reduced accuracy, you can set locationManager.desiredAccuracy = kCLLocationAccuracyReduced to ask for less precise data

Or, set the NSLocationDefaultAccuracyReduced key in Info.plist  ⭢  then the user doesn’t even see the precision option when asked

How it works

The way it works under the hood is that location is “quantized” into sectors roughly a few kilometers in size (smaller in denser urban areas); the location does not continuously update as you move, only when you move to a different sector

The semantics of the approximate location data is that it’s supposed to answer what the user would expect to hear when they ask “Where am I?”

It takes into account things like where one city ends and another starts, or where country borders are

The true location of the user will not necessarily be near the center of the returned area, but it should usually be contained somewhere within the area

The new API is available on iOS & watchOS

All temporary access authorizations are also shared between iOS & watchOS

Change in when in use / always authorization (in 13.4): you can ask for “Always” authorization when the app is in the foreground, after you’ve been granted “While Using” authorization

App clips & widgets

Differences in location authorization for app clips:

  • they can’t get “Always” authorization
  • “While Using” is always granted until tomorrow

Differences in location authorization for widgets:

  • if a widget requires location, it needs to include the NSWidgetWantsLocation key in Info.plist
  • a widget can’t show authorization prompts – it needs to be authorized in the parent app

Streamline your app clip

Categories: App Clips, UIKit, WWDC 20 0 comments Watch the video

(Watch the “Explore app clips” and “Configure and link your app clips” videos before.)

App clips are designed for speed of interaction – interaction needs to be focused on a specific goal and essential tasks

Leave any advanced or complex features for the main app

App clip should be usable immediately when launched

  • include any assets that will be needed immediately in the bundle
  • don’t add a splash screen
  • don’t ask people to log in or sign up before they can perform the task they want
  • ask for permission to access data only if it’s required and only at the moment when you need it

The main app should present the same experience in the same way

The app clip should have the same name and icon as the app

Organizing the project:

Keep in mind that the smaller the app clip bundle is, the faster your users can launch it, so only include what you really need in the bundle

Put shared assets in a separate shared asset catalog

Use Sign in with Apple, ASWebAuthenticationSession or password autofill for logging in

Consider giving an option to upgrade the account to Sign in with Apple if the user logs in using a password

App clips are ephemeral – users should be able to try them out easily while trusting that their privacy is being protected

Some user data is not accessible

You can ask for permission to camera, microphone and Bluetooth

Location access

Don’t ask for location access to determine which physical store the user is in – encode the store ID in the URL

To make sure they’re in the right store, you can use a new API to confirm the location that you already assume you know:

  • add NSAppClipRequestLocationConfirmation to NSAppClip dictionary in the Info.plist
  • get a payload object from userActivity.appClipActivationPayload
  • run payload.confirmAcquired(in: region) { … } (you can set a radius of up to 500m)

Notifications

Ephemeral notifications: you can ask for permission to send the user notifications up to 8 hours after the app clip is accessed

This does not show a regular popup asking for permission (user can refuse access in the initial app clip sheet)

Add NSAppClipRequestEphemeralUserNotification to the dictionary

When ephemeral access is granted, authorizationStatus in UNUserNotificationCenter will be set to .ephemeral

Migrating to the app

After the app clip is activated, a banner is shown at the top of the screen for a moment that leads you to the app’s page in the App Store

You can also use a new SKOverlay from StoreKit to show a popup recommending the app to the user

.appStoreOverlay() in SwiftUI

Do this after they finish the task

Passing data from the app clip to the app: use an app group container

You can also pass Sign in with Apple authorization:

  • get the user ID authorization.credential.user from the Sign in with Apple authorization success handler
  • save it as a file inside the group container (containerURL(forSecurityApplicationGroupIdentifier: “…”))
  • on the app side, read the file and pass it to ASAuthorizationAppleIDProvider: getCredentialState(forUserId: user)

Configure and link your app clips

Categories: App Clips, UIKit, WWDC 20 0 comments Watch the video

(Watch the “Explore app clips” video before.)

App Clips provide an entry point for your users with minimal friction

Touching a physical NFC tag  ⭢  NFC tag encodes a URL  ⭢  your app’s clip configured for that URL shows up as a popup  ⭢  when user presses Open, it’s open in full screen

App clips can also be accessed from: QR codes, Maps, Safari (Smart App Banners), Messages, Siri suggestions

Tools will be available later this year that can create special circular app clip QR codes

Steps to configure:

Configure your web server

Set up the apple-app-site-association file (in .well-known folder)

Add an appclips key:

“appclips”: {
    “apps”: [“ABDE12345.com.example.app”]
}

Configure the app clip for handling links

  • add an associated domains entitlement to the app clip target
  • add a domain prefixed with appclips:, e.g. appclips:example.com
  • handle opening the app with NSUserActivity containing a given URL
  • in SwiftUI: .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { … }
  • in UIKit: scene(_: continue userActivity:)

Once the user has the full app installed, opening the same URL opens the full app instead of the clip – so the app needs to be able to handle the same URLs and show the same content

When testing in Xcode, you can set up a scheme that sets the environment variable _XCAppClipURL to the given URL

Configure app clip experiences on App Store Connect

Prepare metadata for the app clip:

  • Title up to 18 chars
  • Subtitle up to 43 chars
  • Header image: 3000×2000, aspect ratio 3:2, jpeg or png with no transparency

Set up on App Store Connect in a new section that appears once you upload a build with an app clip

If you want the clip to be accessible from more than Safari or Messages (e.g. QR codes), you need to set up “Advanced App Clip Experiences”

One app can set up multiple advanced app clip experiences matching different URLs

URL is matched based on the most specific prefix, so you can have one general catch-all URL and other more specific ones

App Clips can also be accessed by TestFlight testers – configure in “App Clip Invocations” sections in TestFlight on App Store Connect

Configure the Smart App Banner

Standard app banner:

<meta name='apple-itunes-app' content='app-id=12345678'>

For an app clip, add app-clip-bundle-id inside:

<meta name='apple-itunes-app' content='app-clip-bundle-id=com.example.fruta.clip, app-id=12345678'>