🎯 Introduction

Welcome to this tutorial on Building Adaptive UI for iPhone, iPad, and Mac! In this session, we'll create an app called Adaptive Mood, which adjusts its UI dynamically based on ambient light, device posture, and screen size.

We’ll use SwiftUI, leverage Apple’s platform-specific capabilities, and integrate sensors like the ambient light sensor on iPhone and Mac. By the end, you'll have a strong understanding of adaptive design principles and how to build a UI that seamlessly transitions across devices.


📖 Table of Contents

  1. Understanding Adaptive UI
  2. Setting Up the Project
  3. Creating a Responsive Layout with SwiftUI
  4. Detecting Device Type & Adjusting UI
  5. Using the Ambient Light Sensor
  6. Handling Device Posture & Environment
  7. Implementing Multi-Platform Design Patterns
  8. Polishing the UI with Animations & Transitions
  9. Testing Across Devices
  10. Conclusion & Next Steps

1️⃣ Understanding Adaptive UI

Adaptive UI ensures your app looks and behaves optimally on different Apple devices. Apple recommends designing for multiple screen sizes, orientations, and input methods (touch, trackpad, keyboard).

🔹 Why Adaptive UI?

  • Users expect a seamless experience across iPhone, iPad, and Mac.
  • Different devices have unique interaction models (e.g., touch vs. cursor-based navigation).
  • Sensors can help apps react to environmental conditions like brightness and posture.

2️⃣ Setting Up the Project

🎬 Create a New SwiftUI Project

  1. Open Xcode and create a new project.
  2. Select App and name it Adaptive Mood.
  3. Choose SwiftUI as the UI framework.
  4. Ensure the app runs on iOS, iPadOS, and macOS by enabling Mac Catalyst.

3️⃣ Creating a Responsive Layout with SwiftUI

We'll use SwiftUI’s layout system to build a UI that adapts to different screen sizes.

🏗 Define the Main View

import SwiftUI

struct ContentView: View {
    @Environment(\.colorScheme) var colorScheme
    var body: some View {
        VStack {
            Text("Adaptive Mood")
                .font(.largeTitle)
                .padding()
            Spacer()
            Image(systemName: colorScheme == .dark ? "moon.fill" : "sun.max.fill")
                .font(.system(size: 100))
                .foregroundColor(.yellow)
            Spacer()
        }
        .padding()
    }
}

🔹 Explanation:

  • Uses @Environment(\.colorScheme) to detect dark/light mode.
  • Dynamically updates icons and colors based on theme.

4️⃣ Detecting Device Type & Adjusting UI

To adjust UI for Mac, iPad, or iPhone, use UIDevice (for iOS/iPadOS) and ProcessInfo (for macOS).

🔍 Check Device Type

#if os(iOS)
    let deviceType = UIDevice.current.userInterfaceIdiom
    let isPad = deviceType == .pad
#elseif os(macOS)
    let isMac = true
#endif

🎨 Apply Different Layouts

var body: some View {
    Group {
        if isMac {
            MacView()
        } else if isPad {
            iPadView()
        } else {
            iPhoneView()
        }
    }
}

5️⃣ Using the Ambient Light Sensor

We’ll use AVCaptureDevice to access the ambient light sensor.

📡 Read Light Data

import AVFoundation

class LightSensor: ObservableObject {
    @Published var brightness: CGFloat = 1.0
    private var captureSession: AVCaptureSession?

    func start() {
        let device = AVCaptureDevice.default(for: .video)
        device?.addObserver(self, forKeyPath: "ISO", options: .new, context: nil)
    }
}

🎨 Adjust UI Based on Light

struct AdaptiveView: View {
    @ObservedObject var sensor = LightSensor()
    var body: some View {
        VStack {
            Text("Brightness: \(sensor.brightness)")
                .foregroundColor(sensor.brightness < 0.5 ? .white : .black)
        }
    }
}

6️⃣ Handling Device Posture & Environment

📡 Use Accelerometer for Orientation Changes

import CoreMotion

class MotionManager: ObservableObject {
    private var manager = CMMotionManager()
    @Published var isLandscape = false

    func start() {
        manager.startDeviceMotionUpdates(to: .main) { (data, error) in
            if let data = data {
                self.isLandscape = abs(data.gravity.x) > abs(data.gravity.y)
            }
        }
    }
}

🎨 Adapt UI Layout Based on Orientation

@ObservedObject var motion = MotionManager()
VStack {
    if motion.isLandscape {
        HStack { ContentView() }
    } else {
        VStack { ContentView() }
    }
}

7️⃣ Implementing Multi-Platform Design Patterns

🔹 Use NavigationSplitView for Mac & iPad to display multi-column layouts.

NavigationSplitView {
    SidebarView()
} detail: {
    ContentView()
}

8️⃣ Polishing the UI with Animations & Transitions

.withAnimation {
    showDarkMode.toggle()
}
.transition(.opacity)

9️⃣ Testing Across Devices

🔹 Use Xcode Previews: Ensure layouts work on different devices.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .previewDevice("iPhone 14 Pro")
        ContentView()
            .previewDevice("iPad Pro (12.9-inch)")
    }
}

🔟 Conclusion & Next Steps

🎉 Congratulations! You’ve built an adaptive UI app that works across iPhone, iPad, and Mac!

Next Steps:

  • Add Haptic feedback.
  • Implement VoiceOver for accessibility.
  • Deploy and test on real devices.

🚀 Happy Coding!