DEV Community

Anis Ali Khan
Anis Ali Khan

Posted on

Tutorial 25: ๐Ÿ“ฑ Building Adaptive UI for iPhone, iPad & Mac with SwiftUI

๐ŸŽฏ 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()
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”น 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
Enter fullscreen mode Exit fullscreen mode

๐ŸŽจ Apply Different Layouts

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

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

๐ŸŽจ 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)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

๐ŸŽจ Adapt UI Layout Based on Orientation

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

7๏ธโƒฃ Implementing Multi-Platform Design Patterns

๐Ÿ”น Use NavigationSplitView for Mac & iPad to display multi-column layouts.

NavigationSplitView {
    SidebarView()
} detail: {
    ContentView()
}
Enter fullscreen mode Exit fullscreen mode

8๏ธโƒฃ Polishing the UI with Animations & Transitions

.withAnimation {
    showDarkMode.toggle()
}
Enter fullscreen mode Exit fullscreen mode
.transition(.opacity)
Enter fullscreen mode Exit fullscreen mode

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)")
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”Ÿ 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!

Top comments (0)