MacKuba

Kuba Suder's blog on Mac & iOS development

Mac

App ClipsAppKitCloudKitExtensionsFoundationiCloudLocationLoggingMacMapsPerformancePhotosPrivacySafariSwiftSwiftUIUIKitWatchKitWWDC 12WWDC 14WWDC 15WWDC 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

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

What’s New in AppKit for macOS

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

New frameworks

AppKit is no longer the only framework to build Mac apps with – you also have an option now to use UIKit via Catalyst and the new SwiftUI framework

  • although you will probably still need to use at least a little bit of AppKit in both cases

NSColor

New system colors: NSColor.systemTeal and systemIndigo (dynamic colors, specific color values depend on light/dark appearance)

NSColor uses tagged pointers

→ this means that color data is stored in the pointer itself and allocating NSColor objects becomes very cheap

NSColorSampler – a magnifier tool for picking a color from somewhere on the screen

New NSColor initializer that lets you return different colors based on appearance:

let color = NSColor(name: "userWidgetColor") { appearance in
  switch appearance.bestMatch(from: [.aqua, .darkAqua]) {
  case .darkAqua:
    return darkUserWidgetColor
  case .aqua, .default:
    return lightUserWidgetColor
  }
}

Screens

NSScreen.localizedName now returns e.g. “Thunderbolt Display”

Extended dynamic range:

Computer screens are very bright these days and most of the time aren't used at full brightness

This means that at lower brightness levels we can use the monitor's capability to show brighter pixels than what 100% white means at that brightness level, and provide an extended dynamic range, i.e. color values of more than 1.0

This feature has been available in macOS since 10.11:

  • CAMetalLayer.wantsExtendedDynamicRangeContent – enables dynamic range in this layer
  • NSScreen.maximumExtendedDynamicRangeColorComponentValue – tells you the maximum color component value (e.g. 1.3), if some content on the screen is using EDR

New APIs in 10.15:

  • NSScreen.maximumPotentialExtendedDynamicRangeColorComponentValue – tells you the maximum component value even if it’s not rendering in EDR mode at the moment, so that you can make some decisions in advance before you enable it
  • NSScreen.maximumReferenceExtendedDynamicRangeColorComponentValue – maximum usable value on reference screens like the new Pro Display XDR

Finding the current screen in Metal:

Previously, in order to find the MTLDevice for the current screen in Metal you had to do something like this:

let preferredDevice = CGDirectDisplayCopyCurrentMetalDevice(
    self.window?.screen?.deviceDescription["NSScreenNumber"]
)

In 10.15, there's a new property CAMetalLayer.preferredDevice that returns the MTLDevice for the current screen

Also MTKView.preferredDevice

Text & Fonts

NSTextView.usesAdaptiveColorMappingForDarkAppearance – automatically updates colors for light/dark appearance

Enable this if it's more important for rich text to be readable regardless of appearance than to show the document in an original unchanged form

Almost all of the NSText* related classes support secure coding now

Spell checking:

NSSpellChecker – old API for managing spell checking (since OS X 10.0!)

NSTextCheckingController – new API built as a successor of NSSpellChecker

  • used in UIKit, WebKit and AppKit
  • you can add support for it to any view by implementing the NSTextCheckingClient protocol
  • also does grammar checks, data detection, autocorrection

Font descriptors:

NSFontDescriptor.withDesign(.monospaced / .rounded / .serif)

Lets you choose a different variant of the same font, if available

Scaling fonts between macOS & iOS:

If your app opens rich text documents created on another platform, you will notice that fonts at the same point size will look different, because Mac and iOS devices use very different screen densities

There are now new NSAttributedString APIs that let you scale fonts in documents coming from the other platform to a more appropriate size:

  • NSAttributedString.DocumentAttributeKey.textScaling and sourceTextScaling
  • NSAttributedString.DocumentReadingOptionKey.targetTextScaling and sourceTextScaling

Text hyphenation:

Previously, you could configure hyphenation for each paragraph using NSParagraphStyle

Now, this can be enabled for the whole container using NSLayoutManager.usesDefaultHyphenation

Toolbars

Easier way to make standard toolbar buttons: NSToolbarItem.isBordered

Just create an NSToolbarItem with an image and set isBordered = true

Needs to be done in code though, no setting on the storyboard yet

Previously, you had to manually create an NSButton, configure it appropriately and add it as a custom view to the toolbar item

Using NSToolbarItem directly also allows you to make use of its built-in functionality like automatic enabling/disabling

NSToolbarItem.title: allows you to make toolbar items that are buttons with a text label instead of an icon (this is different than the item label, which appears below the button)

NSToolbarItemGroup:

  • new convenience initializers that create a group with given item labels/icons in one line
  • can display items as a segmented control which collapses into a popup or pulldown menu when there isn't enough space (use the new initializers for this)

NSMenuToolbarItem – a button toolbar item that shows a pulldown menu, which can be any NSMenu (with separators, nested menus etc.)

Touch Bar

NSTouchBar.isAutomaticCustomizeTouchBarMenuItemEnabled – lets you enable/disable the "Customize Touch Bar…" entry in the Edit menu

NSApplication already has such property, but this is useful if you want to avoid referencing NSApplication in the Touch Bar related code, or if you don't have access to it (e.g. Catalyst apps don't)

NSStepperTouchBarItem – a new item type for selecing an item from a list, with left/right arrows

NSSliderTouchBarItem: minimumSliderWidth, maximumSliderWidth properties for defining min/max width (previously you could achieve this using AutoLayout constraints, but it was less convenient)

Other controls

NSSwitch – a new NSControl that looks like UISwitch on iOS

Not a replacement for checkbox

Avoid using it for small things and in large numbers, just one for some general mode switch, a "master toggle", like the Time Machine on/off switch

NSCollectionView – added compositional layout, diffable data source (same as on iOS)

Storyboards

Storyboards now let you use custom VC initializers for injecting dependencies:

@IBSegueAction func showFoo(_ coder: NSCoder) -> NSViewController {
    return MyViewController(params...)
}

This lets you use initializers in view controller classes that require passing any data the view controller needs, while still using storyboard segues to connect view controllers on the storyboard

AutoLayout

Controls like text labels, buttons with titles etc. calculate their intrinsic content size that lets the AutoLayout system scale the whole window layout depending on the text contents of those controls

However, in some scenarios (e.g. inside an NSGridView) you want all controls to have their size set externally, and the control's intrinsic size is ignored

The calculations to determine the intrinsic size are still performed though in such case, and now you can manually disable them (as an optimization) by setting these to false if you know the result will not be used in the layout calculations:

  • NSView.isHorizontalContentSizeConstraintActive
  • NSView.isVerticalContentSizeConstraintActive

NSResponder

There was a possible source of bugs before if you captured an instance of a UI-related class in a block in such a way that it could be later deallocated on a background thread:

let label = NSTextField(labelWithString: "...")

dispatch_async(dispatch_get_global_queue(0, 0)) {
    // ...

    dispatch_async(dispatch_get_main_queue()) {
        label.value = "done"
    }
}

If the outer block is released after the inner block (which you have no control over), then at that point the label object is deallocated on the background thread, and this could cause various hard to debug issues. This is now solved in 10.15 – the SDK guarantees that UI objects will be deallocated on the main thread in such scenario.

Privacy & security

Recording the screen will now require asking the user for permission

This doesn't apply to the new NSColorSampler control mentioned earlier, which runs out of process

Open and save panels are also now always out-of-process, even for non-sandboxed apps

You may run into problems if you were somehow subclassing or customizing them in a non-standard way

NSWorkspace

New asynchronous versions of existing methods for opening one or more URLs or applications

You can configure some aspects of opening using NSWorkspace.OpenConfiguration, e.g.:

  • if the user can control the process of opening a URL/app
  • if the opened app or document is added to the "Recents" menu
  • if the launched app is hidden on launch

iPad & Sidecar

With the Sidecar feature, an iPad can function as an additional screen for the Mac, and also as a drawing tablet

Tablet drawing events are sent as normal mouse events, with .tabletPoint subtype

Tablet events also provide pressure data (although unlike on iOS, you can't register to get retroactive updates for previous pressure)

Switching mode on the Apple Pencil by double-tapping can be handled by another event with event type .changeMode

Also an NSResponder in the responder chain can handle that event through a new changeMode(withEvent:) method

You can also listen to that event outside of the responder chain by using the "local event monitor" API:

let monitorId = NSEvent.addLocalMonitorForEvents(matching: .changeMode) { (event) -> NSEvent? in
    switchTool()
    return event
}

Foundation additions

View geometry:

New data types for specifying view geometry:

NSDirectionalRectEdge

NSDirectionalEdgeInsets

NSRectAlignment

These use the terms "leading" & "trailing" on the horizontal axis instead of e.g. "minX" or "maxX", so they work better with right-to-left languages

Formatters:

NSRelativeDateFormatter – allows you to format dates as e.g. "1 month ago" or "last week"

NSListFormatter – formats lists of things, adding commas properly (uses nested formatters from .itemFormatter to format specific items)

Changes in extensions

Non-UI file provider action extension

Network Extensions, DriverKit and Endpoint Security replace old kernel extensions

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)