Screenshot of an extra‑large iOS 27 widget displaying a dashboard with a new paste shortcut button
mobile crossplatformIntermediate

How to Build ExtraLarge Widgets on iOS 27 and Use the New Clipboard Shortcut

September 19, 2026· 10 min read
TL;DR
- iOS 27 introduces an extra‑large widget size (≈30 % more real‑estate on iPhone 14 Pro Max and later), a system‑wide “Paste” shortcut that appears above the keyboard, and a smoother Apple Pay card‑switch button.
- To reap the benefits you must:
1. Upgrade to Xcode 15.4 (or later) and target iOS 27+.
2. Adopt WidgetKit 4.0 – add extraLarge to WidgetSupportedFamilies, respect the 30 ms rendering budget, and cache any data you need before the timeline runs.
3. Add UIPasteConfiguration to every UITextField/UITextView that should accept a specific paste type (URL, email, phone, etc.).
- Early adopters report +12 % to +15 % increase in widget‑driven daily active users, a 22 % reduction in copy‑paste friction, and a 9 % drop in Apple Pay checkout abandonment.

1. Why iOS 27’s UI Changes Matter

Apple’s biggest UI shift in iOS 27 is more space, not more code. The OS gives developers a new widget family, a lock‑screen paste shortcut, and a more fluid payment flow—all without requiring any server‑side changes.

FeatureWhat the user seesWhy it matters to you
---------------------------------------------------
Extra‑large widgetA tile that can occupy up to 4 × 4 grid cells on large iPhones, showing richer visuals, more text, or a mini‑dashboard.Gives you a canvas comparable to a small app screen, letting you surface high‑value content (e.g., live sports scores, health dashboards, or a “quick‑add” to‑do list) without forcing the user to open the app.
Lock‑screen paste shortcutWhen a user copies a URL, phone number, or address, a “Paste” button appears just above the keyboard on the lock screen.Reduces the number of taps needed to fill forms, especially in messaging or note‑taking apps. The shortcut is automatic once you expose the right paste configuration.
Apple Pay card‑switch buttonA card‑switch style button inside the payment sheet that flips to the card selector without leaving the checkout flow.Shortens the checkout funnel, directly addressing the “I want to pay with a different card” pain point.
New clock placement & lock‑screen widget areaThe system clock moves to the top row, freeing the middle row for time‑critical widgets (e.g., countdowns, event reminders).Allows you to design lock‑screen widgets that stay visible even when the user has the clock enabled, improving discoverability.
Landscape support for docked iPhonesiPhones placed in a dock (e.g., on a desk) now rotate to landscape automatically.Users with external keyboards or larger screens get a more desktop‑like experience; you can expose extra UI elements that would otherwise be hidden in portrait.

If you ignore these changes, your app’s widgets will look cramped next to competitors that have already migrated to the extra‑large size. If you embrace them, you can add richer content with relatively little extra code and see measurable engagement lifts.

2. WidgetKit 4.0 and the Extra‑Large Family

2. WidgetKit 4.0 and the Extra‑Large Family
2. WidgetKit 4.0 and the Extra‑Large Family

2.1 What’s New in WidgetKit 4.0

ChangeDescriptionPractical impact
---------------------------------------
WidgetFamily.extraLargeNew enum case that can be requested in the widget’s Info.plist.Gives you a 4 × 4 cell slot on supported devices.
30 ms rendering budgetiOS now measures the time it takes to produce a timeline entry and render the SwiftUI view. If you exceed 30 ms, the system may throttle updates or drop the widget.Forces you to pre‑process data, cache heavy assets, and keep UI code lightweight.
Stricter privacy for location & healthWidgets can no longer request NSLocationWhenInUse or HealthKit permissions directly. The host app must request and forward the data.You need a data‑sharing bridge between the main app and the widget, typically via AppGroup storage.
Improved timeline policiesNew TimelineReloadPolicy.atEnd and TimelineReloadPolicy.afterDate(_:) give finer control over when the system asks for a new timeline.Lets you schedule updates exactly when the data changes (e.g., a sports score at the start of a quarter).

2.2 Adding the Extra‑Large Family to Your Widget

  1. Open Info.plist for the widget target.
  2. Add a new key WidgetSupportedFamilies (type: Array) if it does not already exist.
  3. Insert the string extraLarge alongside any existing families (systemSmall, systemMedium, systemLarge).
xml
<key>WidgetSupportedFamilies</key>
<array>
    <string>systemSmall</string>
    <string>systemMedium</string>
    <string>systemLarge</string>
    <string>extraLarge</string>
</array>
Tip: Keep the array sorted alphabetically; Xcode’s autocomplete will help you avoid typos.

2.3 Detecting the Family in SwiftUI

swift
import WidgetKit
import SwiftUI

struct MyWidgetEntryView : View {
    @Environment(\.widgetFamily) var family
    var entry: MyWidgetEntry

    var body: some View {
        switch family {
        case .systemSmall:
            SmallWidgetContent(entry: entry)
        case .systemMedium:
            MediumWidgetContent(entry: entry)
        case .systemLarge:
            LargeWidgetContent(entry: entry)
        case .extraLarge:
            ExtraLargeWidgetContent(entry: entry)   // <-- your custom layout
        default:
            LargeWidgetContent(entry: entry)       // fallback for future families
        }
    }
}

Implementation notes

  • ✔️Avoid hard‑coding dimensions. Use GeometryReader to read the available size and layout proportionally.
  • ✔️Leverage LazyVGrid/LazyHGrid for dynamic collections (e.g., a list of upcoming events).
  • ✔️Respect the safe area – the extra‑large widget can be placed near the screen edges; use .padding(.horizontal, 8) to keep content readable.

2.4 Staying Inside the 30 ms Rendering Budget

The 30 ms budget covers both the data‑fetching part (getTimeline) and the SwiftUI view rendering. Below are proven strategies to stay under the limit.

#### 2.4.1 Pre‑Cache Network Assets

swift
class ImageCache {
    static let shared = NSCache<NSURL, UIImage>()

    static func preload(urls: [URL]) {
        let queue = DispatchQueue(label: "com.myapp.image-preload", qos: .utility)
        queue.async {
            for url in urls {
                if shared.object(forKey: url as NSURL) == nil {
                    if let data = try? Data(contentsOf: url),
                       let image = UIImage(data: data) {
                        shared.setObject(image, forKey: url as NSURL)
                    }
                }
            }
        }
    }
}
  • ✔️Call ImageCache.preload(urls:) once in the main app (e.g., after a successful API call).
  • ✔️In getTimeline, never perform a network request; read from the cache instead.

#### 2.4.2 Use AppGroup for Shared JSON

swift
let group = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.mycompany.myapp")!
let jsonURL = group.appendingPathComponent("widgetData.json")

func writeWidgetData(_ data: MyWidgetModel) {
    let encoder = JSONEncoder()
    if let json = try? encoder.encode(data) {
        try? json.write(to: jsonURL)
    }
}
  • ✔️The main app updates widgetData.json whenever new data arrives.
  • ✔️The widget reads the file synchronously inside getTimeline, which is nanoseconds compared to a network round‑trip.

#### 2.4.3 Light‑Weight SwiftUI Views

  • ✔️Avoid heavy modifiers like .blur(radius:) or .shadow(radius:) on large images; they trigger off‑screen rendering.
  • ✔️Prefer Image(uiImage:) over AsyncImage for cached images.
  • ✔️Limit view hierarchy depth – each extra VStack/ZStack adds layout passes.

#### 2.4.4 Profiling with Instruments

  1. Open Instruments → Time Profiler.
  2. Run the widget in the Simulator (choose a device that supports extra‑large).
  3. Record the time spent in Provider.getTimeline and in MyWidgetEntryView.body.

If you see > 30 ms, focus on the longest call stack entry (usually a file read or image decode) and apply the caching strategies above.

2.5 Example: Full‑Featured Extra‑Large Weather Widget

Below is a more complete skeleton that demonstrates the concepts discussed.

swift
import Foundation

// MARK: - Model

struct WeatherEntry: TimelineEntry {
    let date: Date
    let temperature: String
    let conditionIcon: UIImage   // Cached image
    let forecast: [String]        // Short text for next 3 hours
}

// MARK: - Provider

struct WeatherProvider: TimelineProvider {
    func placeholder(in context: Context) -> WeatherEntry {
        WeatherEntry(date: Date(),
                     temperature: "--°",
                     conditionIcon: UIImage(systemName: "cloud")!,
                     forecast: ["--", "--", "--"])
    }

    func getSnapshot(in context: Context,
                     completion: @escaping (WeatherEntry) -> Void) {
        let entry = WeatherEntry(date: Date(),
                                 temperature: "72°",
                                 conditionIcon: UIImage(systemName: "sun.max")!,
                                 forecast: ["73°", "71°", "70°"])
        completion(entry)
    }

    func getTimeline(in context: Context,
                     completion: @escaping (Timeline<WeatherEntry>) -> Void) {
        // 1️⃣ Load cached JSON (fast, < 2 ms)
        let group = FileManager.default.containerURL(
            forSecurityApplicationGroupIdentifier: "group.com.mycompany.myapp")!
        let jsonURL = group.appendingPathComponent("weatherCache.json")
        guard let data = try? Data(contentsOf: jsonURL),
              let model = try? JSONDecoder().decode(WeatherModel.self, from: data) else {
            // Fallback – schedule a retry in 15 min
            let retry = Calendar.current.date(byAdding: .minute, value: 15, to: Date())!
            let entry = placeholder(in: context)
            completion(Timeline(entries: [entry], policy: .after(retry)))
            return
        }

        // 2️⃣ Pull cached icon from NSCache
        let icon = ImageCache.shared.object(forKey: model.iconURL as NSURL) ??
                   UIImage(systemName: "cloud")!

        // 3️⃣ Build timeline entries (hourly for the next 6 h)
        var entries: [WeatherEntry] = []
        let now = Date()
        for offset in 0..<6 {
            let entryDate = Calendar.current.date(byAdding: .hour,
                                                  value: offset,
                                                  to: now)!
            let entry = WeatherEntry(date: entryDate,
                                     temperature: model.hourlyTemps[offset],
                                     conditionIcon: icon,
                                     forecast: Array(model.hourlyTemps.prefix(3)))
            entries.append(entry)
        }

        // 4️⃣ Tell iOS to refresh after the last entry
        let refreshDate = Calendar.current.date(byAdding: .hour, value: 6, to: now)!
        completion(Timeline(entries: entries, policy: .after(refreshDate)))
    }
}

// MARK: - View

struct WeatherWidgetEntryView: View {
    var entry: WeatherEntry

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            HStack {
                Image(uiImage: entry.conditionIcon)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 60, height: 60)
                Text(entry.temperature)
                    .font(.system(size: 48, weight: .bold, design: .rounded))
            }
            Divider()
            HStack(spacing: 12) {
                ForEach(entry.forecast, id: \.self) { temp in
                    Text(temp)
                        .font(.system(size: 20, weight: .medium))
                }
            }
        }
        .padding()
    }
}

// MARK: - Widget Declaration

@main
struct WeatherWidget: Widget {
    let kind: String = "WeatherWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind,
                            provider: WeatherProvider()) { entry in
            WeatherWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("MyWeather")
        .description("Shows the current temperature and a short‑term forecast.")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge, .extraLarge])
    }
}

Key take‑aways from the example

  • ✔️All I/O (JSON read, image fetch) happens outside the UI thread, and the data is already cached.
  • ✔️The view uses Image(uiImage:) to avoid the overhead of AsyncImage.
  • ✔️The timeline is hourly, matching the granularity of the weather data and keeping the widget fresh without unnecessary refreshes.

3. Music Widget with AutoMix

Apple added a autoMixTransitionsEnabled flag to MusicKit that smooths cross‑fade transitions for any music‑related widget. This is especially useful for “Now Playing” style widgets that sit on the lock screen or home screen.

3.1 Enabling AutoMix

swift
import MusicKit

struct MusicProvider: TimelineProvider {
    func placeholder(in context: Context) -> MusicEntry {
        MusicEntry(date: Date(), title: "Placeholder", artist: "Artist")
    }

    func getSnapshot(in context: Context,
                     completion: @escaping (MusicEntry) -> Void) {
        completion(MusicEntry(date: Date(),
                              title: "Snapshot Song",
                              artist: "Sample Artist"))
    }

    func getTimeline(in context: Context,
                     completion: @escaping (Timeline<MusicEntry>) -> Void) {
        // Turn on the AutoMix flag before we request any track info
        MusicKit.shared.autoMixTransitionsEnabled = true

        Task {
            let nowPlaying = try? await MusicKit.shared.nowPlaying()
            let entry = MusicEntry(date: Date(),
                                   title: nowPlaying?.song.title ?? "No Song",
                                   artist: nowPlaying?.song.artistName ?? "")

            // Refresh every 5 minutes – enough for most songs
            let refresh = Calendar.current.date(byAdding: .minute, value: 5, to: Date())!
            completion(Timeline(entries: [entry], policy: .after(refresh)))
        }
    }
}

3.2 Building the Visualizer

A lightweight visualizer can be built with Canvas or a custom Shape. Below is a minimal waveform visualizer that respects the 30 ms budget.

swift
struct MusicVisualizer: View {
    @State private var phase: CGFloat = 0

    Canvas { context, size in
        let path = Path { p in
            let amplitude: CGFloat = size.height / 4
            let frequency: CGFloat = 2
            for x in stride(from: 0, through: size.width, by: 2) {
                let relativeX = x / size.width
                let y = amplitude * sin((relativeX + phase) * .pi * frequency) + size.height / 2
                if x == 0 { p.move(to: CGPoint(x: x, y: y)) }
                else { p.addLine(to: CGPoint(x: x, y: y)) }
            }
        }
        context.stroke(path, with: .color(.white.opacity(0.8)), lineWidth: 2)
    }
    .onAppear {
        // Simple animation that updates the phase every 0.05 s
        Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { _ in
            phase += 0.02
            if phase > 1 { phase -= 1 }
        }
    }
}

Performance note: The visualizer runs on the main thread but only draws a few hundred points, keeping the draw time well under the 30 ms limit.

3.3 Trade‑offs & Best Practices

ConsiderationRecommendation
-------------------------------
BatteryAutoMix uses the system audio engine; it is efficient but still consumes a small amount of power. Disable it when the device is in Low Power Mode (ProcessInfo.processInfo.isLowPowerModeEnabled).
NetworkDo not fetch album art inside getTimeline. Pre‑cache the artwork when the user opens the music app and store it in AppGroup storage.
Refresh frequencyA minimum of once per hour is required for extra‑large widgets; however, for music widgets a 5‑minute interval is safe and keeps the now‑playing info fresh without draining the battery.
User privacyIf you display the track title, you must respect the user’s “Show Music Data on Lock Screen” setting (MPNowPlayingInfoCenter.default().nowPlayingInfo). Check the flag before exposing any data.

4. Sharing Full‑Resolution Photos

4. Sharing Full‑Resolution Photos
4. Sharing Full‑Resolution Photos

iOS 27 adds a fullResolution flag to PHSharingOptions, allowing you to share the original file (RAW, HEIC, etc.) directly from a shared album. This eliminates the “Tap to download original” step that many photo‑sharing apps still require.

4.1 Step‑by‑Step Implementation

  1. Request Photo‑Library Access
swift
import Photos

PHPhotoLibrary.requestAuthorization { status in
    guard status == .authorized else {
        // Show an alert that the user must enable access in Settings
        return
    }
    // Continue with album creation
}
  1. Create a Shared Album
swift
func createSharedAlbum(named title: String,
                       completion: @escaping (PHAssetCollection?) -> Void) {
    PHPhotoLibrary.shared().performChanges({
        let request = PHAssetCollectionCreationRequest.forAssetCollection(
            withTitle: title,
            assetCollectionType: .album,
            assetCollectionSubtype: .albumCloudShared)

        // Attach sharing options
        let sharingOptions = PHSharingOptions()
        sharingOptions.fullResolution = true
        request.sharingOptions = sharingOptions
    }, completionHandler: { success, error in
        if success {
            // Fetch the newly created collection
            let fetch = PHAssetCollection.fetchAssetCollections(
                with: .album, subtype: .albumCloudShared, options: nil)
            completion(fetch.firstObject)
        } else {
            print("Album creation failed: \(error?.localizedDescription ?? "unknown")")
            completion(nil)
        }
    })
}
  1. Add Assets to the Shared Album
swift
func addAssets(_ assets: [PHAsset],
               to album: PHAssetCollection,
               completion: @escaping (Bool) -> Void) {
    PHPhotoLibrary.shared().performChanges({
        let request = PHAssetCollectionChangeRequest(for: album)
        request?.addAssets(assets as NSArray)
    }, completionHandler: { success, error in
        if let err = error {
            print("Failed to add assets: \(err.localizedDescription)")
        }
        completion(success)
    })
}

4.2 Trade‑offs

ProCon
----------
Exact image quality – Users receive the original file (RAW, 48 MP, etc.) without a second download step.Higher upload bandwidth – A 15 MB RAW file can take ~12 % longer to upload on a 4G connection.
Simpler UI – No need for a “Download Original” button, reducing UI clutter.Potential storage quota issues – iCloud shared albums have a per‑user quota; large RAW files may fill it faster.
Better for professional workflows – Photographers can share uncompressed assets directly.Longer sharing‑link generation – The system needs to generate a secure link for the full‑resolution file, which can add a few hundred milliseconds (still far below the widget budget).

4.3 Practical Guidance

  • ✔️Show a progress indicator when uploading large files; iOS automatically provides a system‑wide “Uploading…” banner, but a local spinner reassures users.
  • ✔️Offer a “Low‑Resolution” toggle for users on cellular data. You can set options.fullResolution = false based on NetworkReachability.
  • ✔️Test with different file types: JPEG, HEIC, RAW (e.g., .dng). The fullResolution flag works for all, but RAW files may need additional metadata handling on the server side.

5. Lock‑Screen Widget Placement

With iOS 27 the system clock moves to the top row of the lock screen, freeing the middle row for time‑critical widgets (countdowns, live sports scores, or “Next Alarm”). Apple also lets users swipe away the default “Now Playing” widget, creating a cleaner canvas.

5.1 Designing for the New Layout

  1. Detect Clock Overlay Preference – The system exposes UIWindowScene.isClockOverlayEnabled (a Boolean).
swift
if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
   scene.isClockOverlayEnabled {
    // The clock will appear on top of the lock‑screen widget.
}
  1. Add a Transparent Banner (optional) to avoid visual clash with the clock.
swift
struct ClockOverlayBanner: View {
    Color.black.opacity(0.2)
        .frame(height: 30) // Approximate height of the clock bar
        .ignoresSafeArea(edges: .top)
}
  1. Combine with Your Widget Content
swift
struct CountdownWidgetView: View {
    var entry: CountdownEntry

    var body: some View {
        ZStack(alignment: .top) {
            if entry.showClockOverlay {
                ClockOverlayBanner()
            }
            VStack {
                Text(entry.title)
                    .font(.headline)
                Text(entry.timeRemaining)
                    .font(.system(size: 48, weight: .bold, design: .monospaced))
            }
        }
    }
}

5.2 Registering a Lock‑Screen‑Only Widget

swift
struct LockScreenTimerWidget: Widget, LockScreenWidget {
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: "LockScreenTimer",
                            provider: TimerProvider()) { entry in
            CountdownWidgetView(entry: entry)
        }
        .supportedFamilies([.extraLarge]) // Lock‑screen widgets only support extra‑large
        .description("Shows a countdown timer on the lock screen.")
    }
}
Note: The widget will not appear on the home screen if the user disables lock‑screen widgets in Settings → Face ID & Passcode → “Allow Lock Screen Widgets”.

5.3 Trade‑offs

AspectBenefitCost
-----------------------
Visibility – The lock screen is always visible (unless the device is locked with a passcode).Higher impression count; good for urgent notifications (e.g., flight boarding).Must keep the UI minimal; large images can be clipped.
Battery – Lock‑screen widgets are refreshed less aggressively than home‑screen widgets.Lower power impact.You may need to request a TimelineReloadPolicy.atEnd to guarantee a refresh right before a critical event.
User control – Users can hide lock‑screen widgets.Respects privacy.Your widget may never be seen by a subset of users; consider a fallback home‑screen widget.

6. System‑Wide Clipboard Shortcut

iOS 27 now surfaces a Paste shortcut above the keyboard only when the clipboard’s content type matches a field’s declared UIPasteConfiguration. This is a subtle but powerful UX improvement that reduces the “copy → open app → paste” friction.

6.1 Adding a Paste Configuration

swift
import UIKit

class UrlField: UITextField {
    override init(frame: CGRect) {
        super.init(frame: frame)
        // Accept only URL strings (public.url)
        pasteConfiguration = UIPasteConfiguration(
            acceptableTypeIdentifiers: ["public.url"])
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // Optional: Provide a custom preview when the user long‑presses the field
    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        if action == #selector(paste(_:)) {
            // Only enable paste if the clipboard actually contains a URL
            return UIPasteboard.general.hasURLs
        }
        return super.canPerformAction(action, withSender: sender)
    }
}

#### Key points

  • ✔️The type identifier must be a Uniform Type Identifier (UTI) that the system recognises. Common values:
  • ✔️"public.url" – URLs
  • ✔️"public.email-address" – email strings
  • ✔️"public.phone-number" – telephone numbers
  • ✔️"public.plain-text" – generic text (fallback)
  • ✔️Multiple identifiers can be supplied as an array, allowing a field to accept several formats (e.g., a “Contact” field that accepts both email and phone).

6.2 Handling the Paste Action

If you need custom parsing (e.g., stripping URL parameters), override paste(_:).

swift
override func paste(_ sender: Any?) {
    guard let item = UIPasteboard.general.itemProviders.first else { return }
    item.loadObject(ofClass: URL.self) { (url, error) in
        DispatchQueue.main.async {
            if let cleanURL = url?.removingQueryParameters() {
                self.text = cleanURL.absoluteString
            }
        }
    }
}

Utility extension

swift
extension URL {
    /// Returns a copy of the URL without query parameters.
    func removingQueryParameters() -> URL? {
        var components = URLComponents(url: self, resolvingAgainstBaseURL: false)
        components?.query = nil
        return components?.url
    }
}

6.3 Accessibility & Internationalisation

  • ✔️VoiceOver automatically announces the “Paste” button. No extra work needed.
  • ✔️Dynamic Type – The shortcut respects the user’s preferred text size; ensure your text field’s font scales with adjustsFontForContentSizeCategory.

6.4 Overlap with Custom Input Accessory Views

If your view controller uses an inputAccessoryView (e.g., a toolbar with “Done” and “Clear”), the system paste shortcut may cover part of it. Mitigation strategies:

  • ✔️Add vertical padding to the accessory view (toolbar.frame.size.height += 10).
  • ✔️Detect the shortcut’s presence via UIPasteConfigurationSupporting delegate method pasteConfiguration(_:didUpdatePasteboard:) and adjust layout accordingly.

6.5 Trade‑offs

Reduced friction – Users can paste with a single tap, improving form completion rates.Potential misuse – If you accept generic public.plain-text, the shortcut may appear even when the clipboard contains unrelated data, leading to accidental pastes.
Consistent system UI – No need to build a custom “Paste” button that mimics the system style.Limited to iOS 27+ – On older OS versions the shortcut does not appear; you must still provide a fallback “Paste” button for backward compatibility.

7. Landscape Support for Docked iPhones

Apple finally added automatic landscape rotation for iPhones placed in a dock (e.g., on a desk with an external keyboard). While this is a system‑level feature, apps must opt‑in by removing the UIRequiresFullScreen key and handling orientation changes gracefully.

7.1 Removing Full‑Screen Restriction

Open your app’s Info.plist and either delete the UIRequiresFullScreen key or set its value to NO.

xml
<key>UIRequiresFullScreen</key>
<false/>
Caution: If your app uses GameKit or Metal full‑screen exclusive mode, you may need to keep the flag. Test thoroughly.

7.2 Responding to Orientation Changes

#### UIKit (Storyboard / XIB)

swift
class RootViewController: UIViewController {
    override func viewWillTransition(to size: CGSize,
                                     with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        coordinator.animate(alongsideTransition: { _ in
            // Update constraints or layout here
            self.updateLayout(for: size)
        })
    }

    private func updateLayout(for size: CGSize) {
        if size.width > size.height {
            // Landscape – show a side‑by‑side view
            self.sidePanel.isHidden = false
        } else {
            // Portrait – hide side panel
            self.sidePanel.isHidden = true
        }
    }
}

#### SwiftUI (Recommended)

swift
struct AdaptiveRootView: View {
    @Environment(\.horizontalSizeClass) var hSize
    @Environment(\.verticalSizeClass) var vSize

    var body: some View {
        if hSize == .regular && vSize == .compact {
            // Landscape on a docked iPhone
            Sidebar()
            MainContent()
        } else {
            // Default portrait layout
            MainContent()
        }
    }
}

Why SwiftUI works better

  • ✔️The view automatically recomposes when size classes change, eliminating the need for manual viewWillTransition.
  • ✔️You can use @Environment(\.scenePhase) to pause heavy background tasks when the device rotates to landscape (e.g., stop a video preview that isn’t needed).

7.3 Testing Landscape on Docked iPhones

  1. Simulator – Choose a device (e.g., iPhone 14 Pro) → “Features → Rotate Device”. Then enable “Hardware → External Display → Docked”.
  2. Physical device – Place the iPhone in a compatible dock (Apple’s USB‑C dock or a third‑party magnetic dock). Verify that the UI rotates automatically.

7.4 Trade‑offs

AspectBenefitCost
-----------------------
More real‑estate – Landscape gives you extra horizontal space for sidebars, tables, or multi‑column layouts.Must keep the UI minimal; large images can be clipped.
Battery – Docked rotation may increase GPU load.Mitigate by lazy‑loading heavy sub‑views (e.g., only load a chart when the user scrolls to it).
User control – Users can hide lock‑screen widgets.Your widget may never be seen by a subset of users; consider a fallback home‑screen widget.

8. Apple Pay Card‑Switch Button

The PKPaymentButtonStyle.cardSwitch style was added to let users change the active payment card without leaving the checkout sheet. This is a subtle UI change but has measurable impact on conversion.

8.1 Adding the Button

swift
import PassKit

class CheckoutViewController: UIViewController {
    private lazy var payButton: PKPaymentButton = {
        let button = PKPaymentButton(paymentButtonType: .plain,
                                     paymentButtonStyle: .cardSwitch)
        button.addTarget(self,
                         action: #selector(startPayment),
                         for: .touchUpInside)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(payButton)
        NSLayoutConstraint.activate([
            payButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            payButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor,
                                               constant: -20),
            payButton.widthAnchor.constraint(equalToConstant: 200),
            payButton.heightAnchor.constraint(equalToConstant: 44)
        ])
    }

    @objc private func startPayment() {
        let request = PKPaymentRequest()
        request.merchantIdentifier = "merchant.com.mycompany"
        request.countryCode = "US"
        request.currencyCode = "USD"
        request.supportedNetworks = [.visa, .masterCard, .amex]
        request.merchantCapabilities = .capability3DS

        let controller = PKPaymentAuthorizationViewController(paymentRequest: request)!
        controller.delegate = self
        present(controller, animated: true, completion: nil)
    }
}

8.2 What the Card‑Switch Button Does

  • ✔️In‑line card selector – When tapped, the button expands into a small overlay that lists the user’s saved cards. The user can tap a different card, and the payment request updates instantly.
  • ✔️No navigation – The overlay appears above the checkout sheet; the user never leaves the current view hierarchy.

8.3 Best Practices

RecommendationReason
------------------------
Show the button only when multiple cards are available. Use PKPaymentAuthorizationViewController.canMakePayments(usingNetworks:) to query the wallet.Prevents a dead‑end UI where tapping does nothing.
Provide a fallback “Change Card” link for iOS 26 devices that lack the new style.Guarantees a consistent experience across OS versions.
Track the button tap in analytics (Analytics.logEvent("applepaycardswitch_tapped")).Allows you to measure the impact on conversion.
Test with Dark Mode – the button automatically adapts, but verify the overlay’s contrast.Dark Mode is now the default for many users.

8.4 Trade‑offs

Higher conversion – Early adopters see a 9 % drop in checkout abandonment.Limited to iOS 27+ – On older OS versions the shortcut does not appear; you must still provide a fallback “Pay” button for backward compatibility.
Cleaner UI – No need for a separate card‑selection screen.Potential UI clash – If your checkout sheet already has a custom toolbar, the overlay may overlap; add extra bottom padding if needed.

9. Putting It All Together – A Practical Roadmap

Below is an expanded, step‑by‑step migration plan that aligns engineering effort with business impact. Each phase includes concrete tasks, estimated effort, and measurable KPIs.

PhaseGoalConcrete TasksEstimated Effort*KPI / Success Metric
---------------------------------------------------------------------
0 – PreparationEnsure the project can compile with the newest tools.Upgrade Xcode to 15.4.
Set Swift language version to 5.9.
Add iOS 27 as a conditional deployment target (#available(iOS 27, *)).
1‑2 daysBuild succeeds on the latest simulator.
1 – WidgetKit UpgradeAdopt WidgetKit 4.0 and enable the extra‑large family.Add extraLarge to WidgetSupportedFamilies, respect the 30 ms rendering budget, and cache any data you need before the timeline runs.3‑4 daysWidget appears as extra‑large on iPhone 14 Pro Max; timeline generation < 30 ms (measured via Instruments).
2 – Data Caching LayerKeep the rendering budget under 30 ms.Implement ImageCache (NSCache) and JSON cache (AppGroup).
Call ImageCache.preload(urls:) once in the main app.
2‑3 days95 % of timeline entries render under 20 ms (benchmark).
3 – UI Layout for Extra‑LargeProvide a compelling design that uses the new space.Create a SwiftUI view that adapts with @Environment(\.widgetFamily).
Use GeometryReader for dynamic sizing.
2‑4 daysUser‑testing shows > 70 % “looks richer” rating vs. previous large widget.
4 – Clipboard Shortcut IntegrationReduce friction in all text entry points.Add UIPasteConfiguration to every UITextField/UITextView that accepts URLs, emails, or phone numbers.1‑2 daysCopy‑paste completion time drops from ~1.2 s to ~0.3 s (internal measurement).
5 – Full‑Resolution Photo SharingUpgrade photo‑sharing experience.Request photo‑library permission, create shared album with fullResolution, add assets.3‑5 days (including backend changes)12 % increase in “Original photo downloaded” metric; no rise in upload‑failure rate.
6 – Lock‑Screen Widget & Clock OverlayLeverage the new lock‑screen real‑estate.Implement lock‑screen‑only widget with optional clock overlay.2‑3 daysLock‑screen widget impressions rise by 18 % after release.
7 – Landscape Support for Docked iPhonesFuture‑proof UI for docked iPhones.Remove UIRequiresFullScreen, handle orientation changes in UIKit/SwiftUI.2‑4 daysNo layout breakage in landscape; user satisfaction for “tablet‑like” experience improves.
8 – Apple Pay Card‑Switch ButtonReduce checkout abandonment.Replace existing PKPaymentButton with .cardSwitch style, add fallback.1‑2 daysCheckout abandonment drops ≥ 8 % in A/B test.
9 – QA & ReleaseVerify stability across all new features.Run automated UI tests, perform TestFlight beta, monitor metrics.1‑2 weeksNo crashes; all new KPIs meet targets.

\*Effort estimates assume a single iOS developer familiar with SwiftUI and WidgetKit.

Parallel Workstreams

  • ✔️Backend team should expose a high‑throughput endpoint for the widget’s JSON cache (e.g., /widget/v1/summary).
  • ✔️Design team can deliver extra‑large mockups (4 × 4 grid) and clipboard shortcut guidelines in the same sprint.

Rollout Strategy

  1. Feature flag the extra‑large widget and clipboard shortcut behind a remote config (enableExtraLargeWidget).
  2. Gradual rollout – enable for 10 % of users, monitor performance, then increase to 100 %.
  3. Post‑launch monitoring – watch the 30 ms render metric in the “Widget Performance” dashboard; if it spikes, investigate cache misses.

10. Key Takeaways

  • ✔️Extra‑large widgets are a game‑changer for content‑rich experiences; they require WidgetKit 4.0, caching, and SwiftUI‑aware layouts.
  • ✔️30 ms rendering budget forces you to pre‑process data, cache heavy assets, and keep UI code lightweight.
  • ✔️System‑wide clipboard shortcut is now a standard UX. Adding UIPasteConfiguration is a few lines of code but yields a measurable reduction in copy‑paste friction.
  • ✔️Full‑resolution photo sharing removes the “download original” step, improving the workflow for power users and photographers.
  • ✔️Apple Pay card‑switch button reduces checkout abandonment; it’s a small UI tweak with a big conversion impact.
  • ✔️Landscape support for docked iPhones future‑proofs your app for desktop‑like use cases and external keyboards.

By following the roadmap above, you can adopt all of iOS 27’s UI enhancements with a manageable engineering effort and clear business ROI.

Glossary

  • ✔️WidgetKit – Apple framework for creating home‑screen and lock‑screen widgets using SwiftUI.
  • ✔️Timeline entry – A snapshot of data that the widget displays for a specific time interval.
  • ✔️UIPasteConfiguration – API that tells iOS which clipboard data types a text field can accept, enabling the system paste shortcut.
  • ✔️PHSharingOptions.fullResolution – Flag that instructs the Photos framework to share the original file rather than a compressed thumbnail.
  • ✔️PKPaymentButtonStyle.cardSwitch – New Apple Pay button style that opens an inline card selector.
  • ✔️AppGroup – A shared container that lets your main app and widget read/write the same files.
  • ✔️NSCache – In‑memory cache that automatically evicts objects under memory pressure; ideal for image caching in widgets.

Frequently Asked Questions

  1. Do I need to raise my app’s deployment target to iOS 27?

Yes, if you want to ship the extra‑large widget or the clipboard shortcut. You can keep a conditional code path for older OS versions by checking if #available(iOS 27, *).

  1. Will the clipboard shortcut work with third‑party keyboards?

It does. The system presents the shortcut above the keyboard regardless of the keyboard’s origin, as long as the field’s UIPasteConfiguration matches the clipboard content.

  1. How can I stay under the 30 ms rendering budget?
  • ✔️Cache everything (JSON, images).
  • ✔️Avoid any synchronous I/O inside getTimeline.
  • ✔️Use lightweight SwiftUI views (no heavy modifiers).
  • ✔️Profile with Instruments → Time Profiler on a real device.
  1. Can I ship both large and extra‑large widgets?

Absolutely. Include both .systemLarge and .extraLarge in WidgetSupportedFamilies. iOS will automatically select the best size for the device.

  1. Is the Apple Pay card‑switch button mandatory?

No, but data from early adopters shows a 9 % reduction in checkout abandonment, making it a strong best practice.

  1. What happens if a user disables lock‑screen widgets?

The system simply hides the widget; your app should still provide a home‑screen version so the user isn’t left without any widget at all.

  1. Do I need to request location/health permissions again for the widget?

Yes. Widgets can no longer request permissions directly. The host app must request them and then write the data to an AppGroup file that the widget reads.

  1. Can I test the extra‑large widget on an iPhone 13?

No. The extra‑large family is only available on devices with a 4‑column home‑screen grid (iPhone 14 Pro Max and later). Use the simulator or a compatible device.

Read next: continue with one of these related guides.

#iOS 27 widget guidelines#clipboard shortcut iOS#Apple Pay card switch#iOS 27 paste shortcut#extra-large widget#iOS 27 development#iOS 27 widget size#iOS 27 UI changes

Frequently Asked Questions

Do I need to update my app's deployment target to use the extra‑large widget size?+

Yes. The extra‑large widget family is only available on iOS 27 and later, so set the deployment target to 27.0 or conditionally register the widget for earlier OS versions.

Will the new clipboard shortcut work for custom input views?+

It works as long as the text field defines a `UIPasteConfiguration` with accepted type identifiers; custom keyboards inherit the shortcut automatically.

How does the 30 ms rendering budget affect network‑heavy widgets?+

Widgets must preload data and cache it locally; network fetches should happen outside the timeline generation, and only cached data should be used during the 30 ms window.

Can I still use the old `large` widget size alongside `extraLarge`?+

Yes. Include both `.systemLarge` and `.extraLarge` in `WidgetSupportedFamilies`. The system will select the appropriate size based on device screen real‑estate.

Is the Apple Pay card‑switch button required for compliance?+

No, but using `PKPaymentButtonStyle.cardSwitch` improves the checkout flow and reduces abandonment, as shown by early‑adopter metrics.

Dheeraj Ramasahayam
Dheeraj Ramasahayam

Founder & Editor of The Looplet. Sharing fresh technology, coding, and digital insights.

Enjoyed this? Get the weekly digest.

The week's best on engineering, AI, and security — one email, no noise.

Read next

Same categorymobile crossplatform·September 21, 2026

Fatal Fury DLC Datamine vs Switch 2 Backwards Compatibility Fixes: Lessons for PostLaunch Content Strategies

TL;DR – The recent Fatal Fury datamine shows how early asset integration can be leveraged to generate hype, while Nintendo’s systematic Switch 2 compatibility p

Fatal Fury DLC Datamine vs Switch 2 Backwards Compatibility Fixes: Lessons for PostLaunch Content Strategies

Fatal Fury DLC Datamine vs Switch 2 Backwards Compatibility Fixes: Lessons for PostLaunch Content Strategies