I tried to make it possible to select the device to preview in SwiftUI with enum
Specifying the preview device in SwiftUI is troublesome
When specifying the device type in SwiftUI, I think that it is written as follows.
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
.previewDevice(PreviewDevice(rawValue: "iPhone SE"))ViewModifier
.previewDisplayName("iPhone SE")
ContentView()
.previewDevice(PreviewDevice(rawValue: "iPhone XS Max"))
.previewDisplayName("iPhone XS Max")
}
}
}
However, there is a possibility that it is a type miss to write directly as a character string.
And you will spend a barren time, such as getting lost like what should I write in the first place? → troubled → googled. Therefore, I made it possible to select with enum as follows.
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
.previewDevice(device: .iPhone11Pro)
.previewDisplayName(device: .iPhone11Pro)
ContentView()
.previewDevices(devices: [.iPhone11Pro, .iPadPro12_9inch])
}
}
}
Now you don’t have to type miss or wonder what to write as a string.
Implementation details
To achieve the above code, you need to create a custom Modifier. This article was helpful for custom modifiers. Below are the details of the implementation. ① Create an enum called Device
.
enum Device: String {
// iPhone
case iPhoneSE = "iPhone SE"
case iPhone7 = "iPhone 7"
case iPhone7Plus = "iPhone 7 Plus"
case iPhone8 = "iPhone 8"
case iPhone8Plus = "iPhone 8 Plus"
case iPhoneX = "iPhone X"
case iPhoneXs = "iPhone Xs"
case iPhoneXsMax = "iPhone Xs Max"
case iPhoneXR = "iPhone XR"
case iPhone11 = "iPhone 11"
case iPhone11Pro = "iPhone 11 Pro"
case iPhone11ProMax = "iPhone 11 Pro Max"
// iPad
case iPadMini4 = "iPad mini 4"
case iPadAir2 = "iPad Air 2"
case iPadPro9_7inch = "iPad Pro (9.7-inch)"
case iPadPro12_9inch = "iPad Pro (12.9-inch)"
case iPad5th = "iPad (5th generation)"
case iPadPro12_9inch2nd = "iPad Pro (12.9-inch) (2nd generation)"
case iPadPro10_5inch = "iPad Pro (10.5-inch)"
case iPad6th = "iPad (6th generation)"
case iPadPro11inch = "iPad Pro (11-inch)"
case iPadPro12_9inch3rd = "iPad Pro (12.9-inch) (3rd generation)"
case iPadMini5th = "iPad mini (5th generation)"
case iPadAir3rd = "iPad Air (3rd generation)"
// AppleTV
case AppleTV = "Apple TV"
case AppleTV4K = "Apple TV 4K"
case AppleTV4K_1080p = "Apple TV 4K (at 1080p)"
// AppleWatch
case AppleWatchS2_38mm = "Apple Watch Series 2 - 38mm"
case AppleWatchS2_42mm = "Apple Watch Series 2 - 42mm"
case AppleWatchS3_38mm = "Apple Watch Series 3 - 38mm"
case AppleWatchS3_42mm = "Apple Watch Series 3 - 42mm"
case AppleWatchS4_40mm = "Apple Watch Series 4 - 40mm"
case AppleWatchS4_44mm = "Apple Watch Series 4 - 44mm"
case AppleWatchS5_40mm = "Apple Watch Series 5 - 40mm"
case AppleWatchS5_44mm = "Apple Watch Series 5 - 44mm"
}
(2) Create a custom ViewModifier
.
/// View the preview of the selected device.
struct DisplayPreviewDevice: ViewModifier {
let device: Device
func body(content: Content) -> some View {
content
.previewDevice(PreviewDevice(rawValue: device.rawValue))
}
}
// Display all devices selected in Array.
struct DisplayPreviewDevices: ViewModifier {
let devices: [Device]
func body(content: Content) -> some View {
ForEach(devices, id: .self) { device in
content
.previewDevice(PreviewDevice(rawValue: device.rawValue))
}
}
}
// Display the device name of the selected device.
struct PreviewDisplayDeviceName: ViewModifier {
let device: Device
func body(content: Content) -> some View {
content
.previewDisplayName(device.rawValue)
}
}
③Add the created ViewModifier to the extension of View.
extension View {
func previewDevice(device: Device) -> some View {
ModifiedContent(content: self, modifier: DisplayPreviewDevice(device: device))
}
func previewDevices(devices: [Device]) -> some View {
ModifiedContent(content: self, modifier: DisplayPreviewDevices(devices: devices))
}
func previewDisplayName(device: Device) -> some View {
ModifiedContent(content: self, modifier: PreviewDisplayDeviceName(device: device))
}
}
Now you can easily view the preview as well.☺️
Bonus
You may want to specify the Light / Dark mode of the device for display.
By the way, I also created a Modifier that can be easily specified.
enum ColorMode {
case light
case dark
}
struct ColorScheme: ViewModifier {
let color: ColorMode
func body(content: Content) -> some View {
switch color {
case .light:
return content
.environment(.colorScheme, .light)
case .dark:
return content
.environment(.colorScheme, .dark)
}
}
}
extension View {
func colorScheme(color: ColorMode) -> some View {
ModifiedContent(content: self, modifier: ColorScheme(color: color))
}
}
Example
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
.previewDevice(device: .iPhone11Pro)
.previewDisplayName(device: .iPhone11Pro)
// Really write like this
.environment(.colorScheme, .dark)
ContentView()
.previewDevices(devices: [.iPhone11Pro, .iPadPro12_9inch])
// You can write just by specifying with enum!
.colorScheme(.dark)
}
}
}
Summary
By making it possible to specify with enum, it is easier to display Preview.
Also, I think there are times when I want to display multiple Previews, so I thought it would be convenient to be able to specify that with a single method.
However, if you use previewDevices
, you can not specify individual settings such as light mode for iPhone and dark mode for iPad, so it seems that you can use it only when you simply check whether the UI is broken on multiple devices.
I think that the development of SwiftUI will increase in the future, so I hope that SwiftUI will become easier to use in this way.
Important point
Since we have not confirmed that all the device lists that were used in enum Devices are displayed normally in Preview, an error may occur. In that case, I would be grateful if you could tell me secretly. This is official.
Reference materials
– 【SwiftUI】カスタムModifierの作成 – Apple Developer – previewDevice(_:)