MacKuba

Kuba Suder's blog on Mac & iOS development

Mac

App ClipsAppKitCloudKitExtensionsFoundationiCloudLocationLoggingMacMapsPerformancePhotosPrivacySafariSwiftSwiftUIUIKitWatchKitWWDC 12WWDC 14WWDC 15WWDC 16WWDC 18WWDC 19WWDC 20WWDC 21

View as index

What's new in AppKit

Categories: AppKit, Mac, WWDC 21 0 comments Watch the video

Design & control updates

There are design updates for some system controls:

  • popovers appear with an animation
  • sliders smoothly glide into position when clicked
  • smaller things like increased spacing between table sections or slightly wider toolbar buttons

Control tinting:

Individual controls like buttons, segmented controls or sliders can have a custom tint

Properties: NSButton.bezelColor, NSSegmentedControl.selectedSegmentBezelColor, NSSlider.trackFillColor

These properties have been introduced in macOS Sierra for Touch Bar controls; in macOS Monterey they’re functional also for normal app controls

This is useful for specific controls that need to have some kind of semantically meaningful color

→ e.g. a green “Accept Call” and a red “End Call” buttons in a video call app

Avoid confusion with the default button if there is one in the same view, since it will also be colorful

Make sure to indicate purpose with more than just the color (using a clear label or an icon), since some of your users may not be able to distinguish buttons by color

Push buttons:

Push buttons no longer highlight using the accent color on click – they behave just like e.g. segmented controls in macOS Big Sur

Don’t make assumptions about how the highlight state looks (e.g. drawing white text over a button that should be blue when pressed, but will now be light gray)

Instead, check the interior background style NSButtonCell.interiorBackgroundStyle:

  • .normal = colorless state
  • .emphasized = colorful state

The old “regular square button” aka “bevel button” has now been refreshed as “Flexible push style button” and can be used as a variable height push button

It supports the same kind of configuration as a regular push button, so it can serve as a default button and can be tinted

Its corner radius and padding now match other button styles

It can contain larger icons or multi-line text

The vast majority of buttons should still use the standard fixed height push button – the variable height button is meant for special cases

Localizing keyboard shortcuts

Some keyboard shortcuts should be localized for different keyboard layouts, because in some layouts they may be hard or impossible to type, or it may make sense to adapt them for right-to-left languages

E.g. Cmd + \ is not possible to type on the Japanese keyboard, which doesn’t have a backslash key

AppKit can now handle this for you

In macOS Monterey, the system automatically remaps such shortcuts to different ones that are more natural on the given keyboard layout

Shortcuts like Cmd + [ and Cmd + ] to go back and forward will be swapped in right-to-left languages

→ this applies to brackets, braces, parentheses and arrow keys

You can opt out using:

  • NSMenuItem.allowsAutomaticKeyEquivalentMirroring – for directional keys like brackets
  • NSMenuItem.allowsAutomaticKeyEquivalentLocalization – turns off all key localization, including mirroring
  • if you really don’t want to use this feature at all, you can also disable it completely in your app by implementing the NSApplicationDelegate method: applicationShouldAutomaticallyLocalizeKeyEquivalents(_:)

Update to SF Symbols

New version – SF Symbols 3

Expands capabilities of the SF Symbols app

Some symbols now have multiple layers that can be individually colored

Updated format for custom symbols – allows you to annotate distinct layers within an image

Big Sur had two coloring modes for symbols:

  • traditional monochrome template style, drawing the whole symbol using one accent color
  • a multicolor style that uses multiple colors that are predefined in the symbol itself

SF Symbols 3 in macOS Monterey adds two new rendering modes:

  • "hierarchical" – uses a single tint color, but draws different layers of the image in an emphasized or deemphasized way (lighter or darker than the base color)
  • "palette" – lets you assign each layer any custom color independently

APIs for the new rendering modes:

NSImage.SymbolConfiguration(hierarchicalColor: .red)
NSImage.SymbolConfiguration(paletteColors: […])
NSImage.SymbolConfiguration.preferringMulticolor()

Symbol variants:

There are also new APIs for mapping between symbol variants, e.g. outline heart symbol  ⭤  filled heart symbol, or variants with circles etc.

Useful e.g. when you’re building a picker control that uses outline icons for unselected states and filled variants of the same icons for the selected item

To convert between variants, call e.g.: baseImage.image(with: .fill)

There are constants for each kind of symbol variant, and you can combine multiple variants together (e.g. circle + fill)

See “Design and build SF Symbols” for more info

TextKit 2

Huge update to the text system

TextKit is a great text engine with a long track record, used across all Apple systems

However, TextKit is a linear text layout engine, which means it typesets a block of text from the beginning to the end

There are a lot of use cases where a non-linear layout engine is more useful

TextKit 2 always uses a non-linear layout system

This means it can perform layout on a more granular level, which allows it to avoid some unnecessary work

For example, when you’re looking at a middle fragment of a long document, a linear layout system needs to process all text from the beginning up to the given fragment in order to render it; a non-linear system can start at the nearest start of a paragraph

The non-linear layout system also makes it easier to mix text with non-text elements, and improves performance for large documents

TextKit 2 provides a lot of customization points, which allow you to extend its behavior

The new version coexists with TextKit 1, you can choose which engine to use for each view

TextKit 2 has actually already been used in some system apps and controls in Big Sur

See “Meet TextKit 2” for more info

New Swift features

Swift 5.5 introduces some important new features for managing concurrency: async/await and actors

In AppKit, many asynchronous methods that return value through a completion handler now have variants that work with async/await:

@IBAction func pickColor(_ sender: Any?) {
  async {
    guard let color = await NSColorSampler().sample() else { return }
    textField.textColor = color
  }
}

The actor model is a great fit for a UI framework like AppKit where most APIs should be called on a single main thread

The macOS SDK now has a @MainActor property wrapper that marks all types that have to be accessed from the main thread

Classes such as NSView, NSView/WindowController, NSApplication, NSCell, NSDocument etc. are now marked with @MainActor

Code running in the main actor can freely call methods on other main actor types

However, code that isn’t running on the main thread needs to use async/await to run code on a @MainActor type

This is enforced at the compiler level, which lets you avoid common errors that happen when mixing concurrency with UI code

See “Meet async/await in Swift” and “Protect mutable state with Swift actors” for more info

AttributedString:

Swift 5.5 also adds a new value type AttributedString

It has type-safe attributes and a more swifty API for reading & writing attributes

You can easily convert between AttributedString and NSAttributedString

See “What’s new in Foundation” for more info

Updating NSViews:

There is a new Swift property wrapper which should reduce boilerplate around view properties

Let’s say we have a custom view class like this:

class BadgeView: NSView {
  var fillColor: NSColor
  var shadow: NSShadow
  var scaling: NSImageScaling
  …
}

These properties will usually need to have a didSet which updates properties like needsDisplay or needsLayout when they’re modified:

var fillColor: NSColor {
  didSet { needsDisplay = true }
}

The new @Invalidating attribute in NSView lets you easily specify which other view properties should be updated when the given property is modified:

@Invalidating(.display) var fillColor: NSColor

Properties that can be invalidated include: display, layout, constraints, intrinsic content size, restorable state

The marked property needs to be Equatable, since AppKit checks if the value was actually changed before triggering a view update

You can extend the invalidation system by conforming to NSViewInvalidating protocol

Shortcuts

iOS Shortcuts are now available on the Mac

Shortcuts appear in all the places where you can access services today – if your app supports services, it will also support Shortcuts

AppKit decides which shortcuts are available at the given place by checking the responder chain

It asks each responder whether it can provide or receive the type of data used by each shortcut

The types of data are represented by NSPasteboard.PasteboardType (usually a UTI)

To support shortcuts in a given responder object, implement the method:

func validRequestor(forSendType sendType: NSPasteboard.PasteboardType?,
                              returnType: NSPasteboard.PasteboardType?) -> Any

In that method return an instance of a type implementing NSServicesMenuRequestor (usually the same object):

protocol NSServicesMenuRequestor {
  func writeSelection(to pasteboard: NSPasteboard,
                              types: [NSPasteboard.PasteboardType]) -> Bool

  func readSelection(from pasteboard: NSPasteboard) -> Bool
}

Siri Intents

You can now use Siri Intents in a Mac app by adding an Intents Extension

You can also return an intents handler from the application delegate:

protocol NSApplicationDelegate {
  optional func application(_ application: NSApplication,
                        handlerFor intent: INIntent) -> Any?
}

The returned object should conform to an appropriate intent handler protocol, depending on the intent type

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

Meet Safari Web Extensions

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

Existing extension ecosystem:

  • content blockers (iOS & macOS)
  • share extensions – can run JS on the currently opened web page and return data to the extension
  • Safari app extensions on macOS

If you’re a web developer and don’t want to learn Swift to build an extension, or you have an existing extension for Chrome/Firefox/etc., you can now use the new Safari Web Extensions API

Extensions built primarily using HTML, JS and CSS, like legacy Safari extensions

API compatible with other browsers (the WebExtensions standard)

Improved user privacy controls

Extensions are sold through the App Store

Some WebExtensions APIs are missing, so provide feedback if you want something added

Like other extensions, Web Extensions must be packaged inside a native Mac app

Xcode 12 is required to build and run

A command-line tool is provided which wraps an exising web extension (e.g. for Chrome/Firefox) into a new app:

xcrun safari-web-extension-converter […] /path
  • lets you know if any features are not available
  • the largest icon in the manifest is used as the app icon (it’s recommended to include 512×512 and 1024×1024 icons)

To create a new extension from scratch, create a “Safari Extension App” project or add a “Safari Extension” target, and choose Type = Safari Web Extension.

Extension privacy

If your extension needs access to specific sites, the user will be asked for permission to run it on that site for one day or always

Optional permissions: you can include the URL pattern under optional_permissions key and then ask for access using browser.permissions.request(…) at the moment when you require access

The Safari preferences window page of your extension shows information about what kind of access was granted to the extension

It’s best to use the activeTab permission, which grants access to the currently open page when the user interacts with your extension in some way

The hostname of the extension changes every time Safari is launched in order to prevent fingerprinting

→ use browser.runtime.getURL(“/path/to/resource”) to create URLs to assets

Debugging

Access the background page through the Develop menu

Content scripts are visible in the Sources tab for the page

  • to run JS in the console in the context of a content script, choose the script from the pulldown menu in the corner

Don’t rely on code being executed when the page loads, since the extension may not have permission to run yet at this point

Communicating between components

Content script  ⭢  background page:

browser.runtime.sendMessage()

browser.runtime.onMessage.addListener()

Background page  ⭢  extension:

browser.runtime.sendNativeMessage()

Handled by the SafariWebExtensionHandler.beginRequest(with context:) delegate method

→ requires nativeMessaging permission

Extension  ⭢  background page:

Use completion handler from NSExtensionContext object in SafariWebExtensionHandler.beginRequest() to send back a response

App  ⭢  background page:

SFSafariApplication.dispatchMessage() (check that extension is turned on first)

App  ⭤  extension:

Shared NSUserDefaults from an app group, or NSXPCConnection

Adopt the new look of macOS

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

macOS has an all new design

A new toolbar with inline title, big bold controls and integration with the window’s split view

Full height sidebars with colorful icons and updated symbol iconography

New appearance for lists/tables using a new inset selection style

Most of these changes happen automatically to your app (if you’re using classes like NSToolbar and NSSplitViewController)

Sidebars

Sidebar: use NSSplitViewController, items configured using NSSplitViewItem.Behavior.sidebar

Use full size content window mask – NSWindow.StyleMask.fullSizeContentView, so that content is laid out in the space normally taken by the title bar

NSView properties to find the safe areas of the view: safeAreaInsets, safeAreaLayoutGuide, safeAreaRect

Also available on the storyboard (“Safe Area Layout Guide”)

New view item in the library: Window Controller with Sidebar (NSWindowController + NSSplitController)

Opting out of the full height sidebar: NSSplitViewItem.allowsFullHeightLayout

  • use this when sidebar is typically collapsed, or when you need more space for the toolbar

By default all sidebar icons are colored with user's chosen accent color

Use NSOutlineViewDelegate method outlineView(_: tintConfigurationForItem:) to customize

Return an instance of NSTintConfiguration:

  • .default – always uses the system accent color
  • .monochrome – gray monochrome
  • init(preferredColor:) – use this color when default (“rainbow”) accent color is used, but follow the accent color if it’s customized
  • init(fixedColor:) – a fixed color that is always used (e.g. the yellow star in Mail’s VIP folder)

Use sidebar colors to distinguish different sections of the sidebar, or highlight a specific item like the VIP star

Or use monochrome to de-emphasize groups

Toolbars

There’s no longer any special material behind the toolbar items, it’s a uniform part of the content of the window

Works automatically

New toolbar styles, controlled through NSWindow.toolbarStyle

Types of NSWindowToolbarStyle:

.unified

  • the new standard – like in the new versions of system apps
  • larger controls, bold icons
  • inline title located at the leading edge of the title bar next to the sidebar
  • good choice for most windows

.unifiedCompact

  • more compressed style
  • regular sized controls, smaller toolbar height
  • this is what was previously used if the window was configured to hide the title bar
  • now supports an optional inline title
  • use when user’s focus should be on the content of the window and there aren’t many elements in the toolbar

.preference

  • specifically designed for preference windows
  • automatic when you’re using NSTabViewController with the .toolbar tab style

.expanded

  • what used to be the standard layout of the toolbar
  • title is centered on top of the toolbar and can expand across the window
  • large button icons with labels below
  • use when the window title is long, or the toolbar is heavily populated with items, or when you want to keep existing toolbar layout

.automatic

  • default value
  • determines the toolbar style based on your window structure
  • existing apps linked on older SDKs keep their old layout

Toolbar buttons no longer have a border, a shape only appears when hovering

Controls with text fields show a slight border so that you know where you can click to focus

NSToolbarItem minSize & maxSize properties are deprecated – macOS can automatically give your controls a proper size

You can still use constraints if necessary

New NSWindow.subtitle property – shows a smaller subtitle under the window title, e.g. the message count in Mail

In .expanded style it appears next to the title

Controls like back/forward buttons should be put on the leading edge of the title bar, before the title

Set NSToolbarItem.isNavigational to position them there

Users can add and remove them from the toolbar, but can only put them in the leading edge area

NSSearchToolbarItem:

  • new toolbar control for search text fields
  • appears as a text field if there’s enough space, otherwise collapsed into an icon
  • use the searchField property to access the text field itself
  • works on older versions of macOS

NSTrackingSeparatorToolbarItem:

  • a separator that extends the separator line of the window content’s split view upwards through the toolbar
  • when creating, you pass it the split view to track and the index of its divider

To position items in the title bar area of the sidebar, include an NSToolbarItem.Identifier.sidebarTrackingSeparator item and add those items *before* the sidebar separator item

The toolbar has no border below, but a slight shadow appears below it to separate it from the content when the content is scrolled

  • this happens if the scroll view fills the frame and you’re using .fullSizeContentView
  • otherwise, there will be a separator regardless of the scrolling position
  • customize toolbar separator in the window using NSWindow.titlebarSeparatorStyle, or in split view per section using NSSplitViewItem.titlebarSeparatorStyle

Controls

New modern design of controls like popup buttons, sliders, segmented controls

New multicolor system accent color – uses each app’s preferred accent color

Define the app’s global accent color in the asset catalog (can be different for light/dark mode) + set the name in build options

People can still chose one of the previously available accent colors, and then that color overrides your setting so they can use that color everywhere

It’s preferred to use named colors like controlAccentColor, selectedContentBackgroundColor, keyboardFocusIndicatorColor instead of explicitly using your own color for controls

New .large control size, e.g. when you need one large action button

Works for: a few kinds of buttons, text fields, search fields, segmented controls

Also used in the unified toolbar style, and in system alerts

New inset style for table selection

Adds extra padding, taller default row heights

NSTableView.style:

  • .automatic (default)
  • .fullWidth – edge to edge selection background, like previously
  • .inset
  • .sourceList – new appearance of sidebar source list

Automatic style uses .inset by default (on apps built on the latest SDK), .fullWidth in bordered scroll views, .sourceList in source lists

→ you can check the effectiveStyle property to see what style is actually used

The old SelectionHighlightStyle.sourceList setting is deprecated

Text

System text styles are now available (Large Title, Headline, Body etc.) – but without Dynamic Type, so they have one constant size

NSFont.preferredFont(forTextStyle: options:)

NSFontDescriptor.preferredFontDescriptor(forTextStyle: options:)

Symbol images

SF Symbols is now available on the Mac

They can scale to different font sizes and weights

Toolbar and sidebar items automatically configure symbol images to match the size & style of the container

NSImage.init?(systemSymbolName: accessibilityDescription:)

It’s best to use them inside NSImageView (see symbolConfiguration property)

To customize symbol configuration, use NSImage.withSymbolConfiguration(…)

Most of existing named system images now return some kind of symbol image from SF Symbols

Advances in macOS Security

Categories: Mac, WWDC 19 0 comments Watch the video

Defense in depth: there isn’t any single layer that can always perfectly protect you, so there are multiple layers of security, so if any single layer fails that doesn’t defeat the whole security of the system

Layers can delay the advance of the attacker, reduce the attack surface, create “choke points” that are easier to defend

Gatekeeper: designed to protect users from running malicious software, while allowing them to use the software they choose

What does Gatekeeper check:

  • does the app contain any known malicious content?
  • has the software been tampered with since it was signed?
  • does it meet the security policy configured on the computer?
  • first launch prompt  ⭢  does the user actually want to run this?

On Mojave, Gatekeeper runs the check on the 1st launch of quarantined software launched via Launch Services

Quarantine – a technology on macOS for marking files that arrived from some external source (website, airdrop, iMessage, email)

  • includes metadata about where the file came from
  • opt-in – the app has to opt-in to this, so e.g. when apps download their own updates they are usually not quarantined, except for sandboxed apps

Launch Services – a framework for finding and launching apps on macOS, used when launching apps from Finder, NSWorkspace, document handlers etc.

What does not use Launch Services: NSTask, NSBundle/dlopen, exec/posix_spawn

In macOS Catalina:

  • all new software must be notarized to pass Gatekeeper
  • all software is checked when first launched, even when launching through those non-LaunchServices methods
  • all software (even not quarantined) is checked for malicious content on every launch

"You can always choose to run any software on your system" – there will always be a way to run a specific piece of software that you want to run

“We want to make macOS just as secure as iOS, while still maintaining the flexibility that you’ve come to expect from your Mac”

Platform security is increasingly reliant on validity of code signatures; that means if code has no signature, it’s impossible to detect tampering

In a future version of macOS, unsigned code will not load by default, so:

  • sign and notarize all software
  • don’t modify signed applications and bundles
  • handle failures when loading libraries

Privacy changes:

Requires user confirmation for:

  • screen recording
  • keyboard input monitoring

Requires confirmation for access to:

  • Desktop, Documents, Downloads
  • iCloud Drive and third-party cloud storage
  • Removable and network volumes

But:

  • *not* required for creating new files, only for reading existing files
  • tries to understand intent, e.g. doesn’t ask if user double-clicked a file in Finder, or drag&dropped it, or used an open/save panel
  • declare handled CFBundleDocumentTypes with NSIsRelatedItemType to e.g. automatically have access to a subtitles file when opening a movie file

Purpose strings are accepted, but not required (NSDesktopFolderUsageDescription etc.)

Open and save panels always run out of process

Be careful with:

panel(_:userEnteredFilename:confirmed:)

panel(_:validate:)

panel(_:didChangeToDirectoryURL:)

Checking for readability without triggering a consent dialog: isReadableFile, isWritableFile, access()

Apps and other binaries that have previously been denied access to some kind of directory now appear automatically in the "Security & Privacy" access list, unchecked

Full disk access now required for access to Trash (except files that your app has moved there)