Dynamic Island Integration

[email protected]

Dynamic Island Integration

What is a dynamic island?

Dynamic Island is one of Apple’s most innovative features. Apple introduced it with the iPhone 14 Pro series. It replaces the notch and adapts to your activities. This design offers a smooth user experience. It shows system alerts, background tasks, and interactive notifications. The Dynamic Island feature is a new UI element on iOS platforms that helps show notifications and pop-up components. It adds dynamics and interactiveness.

This feature enhances user engagement by allowing real-time updates for apps such as:

  • Music playback (e.g., Spotify, Apple Music)
  • Timers and countdowns
  • Live navigation
  • Food delivery and ride-tracking apps
  • Call notifications

As iPhone Dynamic Island becomes more popular, developers want to add it to their apps. This guide shows you how to integrate Dynamic Island with SwiftUI. It includes technical steps, code snippets, and best practices.

How to use dynamic island SwiftUI?

It's common practice in iOS applications development to use Live Activities and WidgetKit to create real-time updates. These updates will show up on Dynamic Island.

Step 1: Setting Up the Project

Before you start using the iPhone Dynamic Island, ensure you have:

  • Xcode 14 or later
  • iOS 16 or later
  • A real iPhone 14 Pro (or newer) (since Live Activities don’t work on the simulator).
  • The WidgetKit framework supports live activities.
  • Xcode enables the correct Dynamic Island settings.

After this

  • Open Xcode and create a new project.
  • Select “App” under iOS.
  • Choose Swift as the language.
  • Name your project and set other options as needed.

Configure Info.plist

  • You may need to add certain permissions or features if your app needs them. But, for basic Dynamic Island use, the default settings should be enough.

Enable Push Notifications to Your App:

  • Open the project settings in Xcode.
  • Select your target and go to the “Signing and Capabilities” tab.
Xcode's 'Signing & Capabilities' tab for the 'Dynamic Island' project, showing signing settings with automatic signing enabled and team selection. Red arrows highlight the 'Capability' button and the 'Signing & Capabilities' tab
  • Click the “+” button and choose “Push Notifications.”
Xcode's 'Signing & Capabilities' tab with a search for 'Push Notifications.' A pop-up window displays the 'Push Notifications' capability, with a red arrow pointing to it.

Step 2: Creating Live Activity Attributes

To use Dynamic Island, we define Activity Attributes that describe the live event.

import ActivityKit
import Foundation

struct WidgetAttributes: ActivityAttributes, Identifiable, Equatable {
    public typealias Content = ContentState
    
    // MARK: - ContentState
    public struct ContentState: Codable, Hashable {
        var tokenName: String
    }
    
    // MARK: - Properties
    var id = UUID()
    var price: Double
    var percentage: Double
    var name: String
    var isSignalUp: Bool
    
    static func == (lhs: Self, rhs: Self) -> Bool {
        lhs.id == rhs.id
    }
}

Step 3: Creating the Live Activity View

This step defines how Live Activities will appear on the iPhone Dynamic Island.

Create a Widget Extension

  1. Select Project Navigator and Click the “+” button.
Xcode's 'General' tab for the 'Dynamic Island' project, showing deployment settings and identity details. Red arrows highlight the project tab and the '+' button in the bottom left.
  1. Select Widget Extension and Click the “Next” button.
Xcode's 'General' tab for the 'Dynamic Island' project, showing deployment settings and identity details. A pop-up window displays the Widget Extension.
  1. Write the product name, for example, "Dynamic Island Widget," and then click the "Finish" button.
Xcode's 'General' tab for the 'Dynamic Island' project. A pop-up window displays the next step of Widget Extension, where the name of the product should be added.
  1. Also, enable support for Live Activities in the Info.plist file.

Dynamic Island Project Info.plist file interface, displaying the menu where the "Support for Live Activites" are enabled.
  1. Update AppWidget.swift
import SwiftUI
import WidgetKit

// MARK: - AppWidget (Main Widget)
struct AppWidget: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: WidgetAttributes.self) { context in
            LiveActivityView(context: context.attributes)
        } dynamicIsland: { context in
            let viewModel = WidgetViewModel(attributes: context.attributes)
            return DynamicIsland {
                DynamicIslandExpandedRegion(.leading) {
                    imageWithTextPair(imageName: "apple.logo", text: "Signal Up", fontSize: 12)
                }
                DynamicIslandExpandedRegion(.trailing) {
                    expandedTrailingView()
                }
                DynamicIslandExpandedRegion(.center) {
                    expandedCenterView(viewModel: viewModel)
                }
                DynamicIslandExpandedRegion(.bottom) {
                    expandedBottomView(viewModel: viewModel)
                }
            } compactLeading: {
                compactLeadingView(viewModel: viewModel)
            } compactTrailing: {
                compactTrailingView()
            } minimal: {
                AnyView(EmptyView())
            }
            .keylineTint(.cyan)
        }
    }
}

// MARK: - Views
func expandedBottomView(viewModel: WidgetViewModel) -> some View {
    HStack {
        expandedButtonView(viewModel: viewModel, title: "Signal Up", color: .customGreen, 
isSignalUp: viewModel.attributes.isSignalUp)
        expandedButtonView(viewModel: viewModel, title: "Signal Down", color: .customRed, 
isSignalUp: !viewModel.attributes.isSignalUp)
    }
}

func expandedButtonView(viewModel: WidgetViewModel, title: String, color: Color, isSignalUp: Bool) -> some View {
    if let generatedURL = viewModel.generateURL() {
        return AnyView(
            Link(destination: generatedURL) {
                Text(title)
                    .font(.system(size: 14, weight: .semibold))
                    .padding(7)
                    .frame(maxWidth: .infinity)
                    .background(isSignalUp ? color : .gray.opacity(0.2))
                    .clipShape(Capsule())
                    .foregroundColor(isSignalUp ? .black.opacity(0.8) : color)
            }
        )
    } else {
        return AnyView(Text("Invalid URL").foregroundColor(.white))
    }
}

func expandedCenterView(viewModel: WidgetViewModel) -> some View {
    VStack(spacing: 6) {
        HStack(spacing: 0) {
            Image("solana-logo")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 20, height: 20)
            
            Text(viewModel.attributes.name)
                .font(.system(size: 12, weight: .semibold))
                .foregroundColor(.white)
        }
        .padding(.horizontal)
        
        HStack(spacing: 0) {
            Text(viewModel.formattedPrice())
                .font(.system(size: 26, weight: .semibold))
                .foregroundColor(.white)
            
            Text(viewModel.formattedPercentage())
                .font(.system(size: 10, weight: .semibold))
                .padding(4)
                .background(viewModel.signalColor())
                .clipShape(Capsule())
                .foregroundColor(.black)
        }
    }
}

func compactLeadingView(viewModel: WidgetViewModel) -> some View {
    HStack(spacing: 4) {
        Image("solana-logo")
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width: 24, height: 24)
        
        Text(viewModel.formattedPercentage())
            .font(.system(size: 12, weight: .semibold))
            .foregroundColor(viewModel.signalColor())
    }
    .padding(.leading, 6)
}

func compactTrailingView() -> some View {
    Image("chart")
        .resizable()
        .aspectRatio(contentMode: .fit)
        .frame(width: 45, height: 20)
}

func expandedTrailingView() -> some View {
    HStack(spacing: 6) {
        Text("Open App")
            .font(.system(size: 12, weight: .semibold))
            .foregroundColor(.white)
        
        Image(systemName: "chevron.right")
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width: 8, height: 8)
            .foregroundColor(.white)
    }
    .frame(width: 80)
}

func imageWithTextPair(imageName: String, text: String, fontSize: CGFloat) -> some View {
    HStack(spacing: 6) {
        Image(systemName: imageName)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width: 14, height: 14)
            .foregroundColor(.white)
        Text(text)
            .font(.system(size: fontSize, weight: .semibold))
            .foregroundColor(.white)
    }
    .frame(width: 80)
}

Step 4: Creating a Live Activity

// MARK: - Create Activity
func createActivity() {
    let signal = generateRandomSignal()
    let attributes = WidgetAttributes(
        price: signal.price,
        percentage: signal.percentage,
        name: signal.name,
        isSignalUp: signal.isSignalUp
    )
    let content = WidgetAttributes.Content(tokenName: signal.name)
    
    do {
        // Request a new activity
        let activity = try Activity<WidgetAttributes>.request(
            attributes: attributes,
            content: ActivityContent(state: content, staleDate: nil),
            pushType: .token
        )
        
        // Update state after activity creation
        DispatchQueue.main.async {
            self.currentActivity = activity
            self.activityID = UUID()
        }
    } catch {
        // Handle any errors that may occur
        print("Error requesting activity: \(error.localizedDescription)")
    }
}

Step 5: End the Live Activity

// MARK: - End Activity
func endActivity() {
    guard let activity = currentActivity else { return }
    
    let endContent = ActivityContent(state: WidgetAttributes.Content(tokenName: "Ended"), staleDate: Date())
    
    // MARK: - End activity asynchronously
    Task {
        await activity.end(endContent, dismissalPolicy: .immediate)
        DispatchQueue.main.async {
            withAnimation(.easeInOut(duration: 0.5)) {
                self.currentActivity = nil
                self.activityID = UUID()
            }
        }
    }
}

Conclusion

You can show real-time updates on the iPhone 14 Pro and newer models. Integrate Dynamic Island with SwiftUI and Apple Live Activities. By following this guide, you have:

  • Enabled Dynamic Island settings in Xcode.
  • Defined Live Activity Attributes using ActivityKit.
  • Created a widget extension to display updates on Dynamic Island.
  • Requested and started a live activity when an event begins.
  • Updated Live Activity Content to reflect changes in real-time.
  • End the Live Activity when the event is complete.

You can use SwiftUI and WidgetKit to create interactive experiences. This keeps users informed without having to open the app. Dynamic Island boosts user engagement by showing important updates. These include order tracking, sports scores, ride-sharing progress, and more.

Test your setup on a real iPhone 14 Pro or newer. Check for smooth transitions and updates.

Okay, let's remember this

If you are experienced in custom mobile app development: android apps development services, or iOS app development, or you provide native app development services, you should always step in parallel with technology to produce what today's user wants.

Dynamic Island is Apple's way of turning the iPhone's notch into something useful. Instead of a static black cutout, it's an interactive area that changes shape to show important updates (incoming calls, music playing, timers, or directions). The coolest thing - without interrupting what you're doing.