SwiftUI + PreviewDevice

Previewing SwiftUI in Xcode allows for specifying the displayed device, but requires the use of a stringly typed API. Wouldn't it be nice if it used an Enum instead?

SwiftUI + PreviewDevice

Previewing SwiftUI in Xcode allows for specifying the displayed device, but requires the use of a stringly typed API.

ContentView()
	.previewDevice("iPhone SE")

The full list of strings you can use are available in the source code, but wouldn't it be nicer if the previewDevice() modifier accepted an enum instead?

ContentView()
	.previewDevice(.iPhone_SE)

Here's how:

extension View {
	func previewDevice(_ value: PreviewDevice.AvailableDevices) -> some View {
		return self.previewDevice(PreviewDevice(stringLiteral: value.rawValue))
	}

	func previewDisplayName(_ value: PreviewDevice.AvailableDevices) -> some View {
		return self.previewDisplayName(value.rawValue)
	}

	func previewDeviceWithName(_ value: PreviewDevice.AvailableDevices) -> some View {
		return self
			.previewDevice(value)
			.previewDisplayName(value)
	}
}

First we extend View to overload the previewDevice() modifier so it accepts our new PreviewDevice.AvailableDevices enum. We use the rawValue backing each case to use in the original modifier. While we're at it, we also update the previewDisplayName() modifier to take our enum, and add a new previewDeviceWithName() modifier to combine both modifiers into one. That should make configuring our previews easier.

Now we need to define the AvailableDevices enum in an extension to PreviewDevice, ensuring it is backed by a string, and that we include a case for any device we are interested in.

extension PreviewDevice {

	/// An enum of all implemented device types.
	///
	/// * The raw string value must exactly match the input expected by SwiftUI's `.previewDevice()` modifier.
	/// *  Valid and complete as of Xcode 12.3 on Jan 11, 2021.
	/// * Get a list of available simulators by running this command in terminal:
	/// `xcrun simctl list devicetypes`
	enum AvailableDevices: String, CaseIterable {
		// MARK: Mac
		case mac 			= "Mac"
		case macCatalyst 	= "Mac Catalyst"

		// MARK: iPhone
		case iPhone_4s 				= "iPhone 4s"
		case iPhone_5 				= "iPhone 5"
		case iPhone_5s 				= "iPhone 5s"
		case iPhone_6 				= "iPhone 6"
		case iPhone_6Plus 			= "iPhone 6 Plus"
		case iPhone_6s 				= "iPhone 6s"
		case iPhone_6sPlus 			= "iPhone 6s Plus"
		case iPhone_SE1				= "iPhone SE (1st generation)"
		case iPhone_7 				= "iPhone 7"
		case iPhone_7Plus 			= "iPhone 7 Plus"
		case iPhone_8 				= "iPhone 8"
		case iPhone_8Plus 			= "iPhone 8 Plus"
		case iPhone_X 				= "iPhone X"
		case iPhone_Xs 				= "iPhone Xs"
		case iPhone_XsMax 			= "iPhone Xs Max"
		case iPhone_Xr 				= "iPhone Xʀ"
		case iPhone_11 				= "iPhone 11"
		case iPhone_11Pro 			= "iPhone 11 Pro"
		case iPhone_11ProMax 		= "iPhone 11 Pro Max"
		case iPhone_SE2 			= "iPhone SE (2nd generation)"
		case iPhone_12Mini 			= "iPhone 12 mini"
		case iPhone_12 				= "iPhone 12"
		case iPhone_12Pro 			= "iPhone 12 Pro"
		case iPhone_12ProMax 		= "iPhone 12 Pro Max"

		// MARK: iPod Touch
		case iPodTouch 		= "iPod touch (7th generation)"

		// MARK: iPad
		case iPad_2 			= "iPad 2"
		case iPad_Retina 		= "iPad Retina"
		case iPad_5 			= "iPad (5th generation)"
		case iPad_6 			= "iPad (6th generation)"
		case iPad_7 			= "iPad (7th generation)"
		case iPad_8		 		= "iPad (8th generation)"

		// MARK: iPad Air
		case iPad_Air  		= "iPad Air"
		case iPad_Air2 		= "iPad Air 2"
		case iPad_Air3 		= "iPad Air (3rd generation)"
		case iPad_Air4 		= "iPad Air (4th generation)"

		// MARK: iPad Mini
		case iPad_Mini2 			= "iPad mini 2"
		case iPad_Mini3 			= "iPad mini 3"
		case iPad_Mini4 			= "iPad mini 4"
		case iPad_Mini5		 		= "iPad mini (5th generation)"

		// MARK: iPad Pro
		case iPad_Pro97 		= "iPad Pro (9.7-inch)"
		case iPad_Pro129_1 		= "iPad Pro (12.9-inch) (1st generation)"
		case iPad_Pro129_2 		= "iPad Pro (12.9-inch) (2nd generation)"
		case iPad_Pro105 		= "iPad Pro (10.5-inch)"
		case iPad_Pro11_1 		= "iPad Pro (11-inch) (1st generation)"
		case iPad_Pro129_3 		= "iPad Pro (12.9-inch) (3rd generation)"
		case iPad_Pro11_2 		= "iPad Pro (11-inch) (2nd generation)"
		case iPad_Pro129_4 		= "iPad Pro (12.9-inch) (4th generation)"

		// MARK: Apple TV
		case appleTV 			= "Apple TV"
		case appleTV_4K 		= "Apple TV 4K"
		case appleTV_4K_1080p 	= "Apple TV 4K (at 1080p)"

		// MARK: Apple Watch
		case watch_38		 		= "Apple Watch - 38mm"
		case watch_42		 		= "Apple Watch - 42mm"
		case watch_S2_38 			= "Apple Watch Series 2 - 38mm"
		case watch_S2_42 			= "Apple Watch Series 2 - 42mm"
		case watch_S3_38 			= "Apple Watch Series 3 - 38mm"
		case watch_S3_42 			= "Apple Watch Series 3 - 42mm"
		case watch_S4_40 			= "Apple Watch Series 4 - 40mm"
		case watch_S4_44 			= "Apple Watch Series 4 - 44mm"
		case watch_S5_40 			= "Apple Watch Series 5 - 40mm"
		case watch_S5_44 			= "Apple Watch Series 5 - 44mm"
		case watch_SE_40 			= "Apple Watch SE - 40mm"
		case watch_SE_44 			= "Apple Watch SE - 44mm"
		case watch_S6_40 			= "Apple Watch Series 6 - 40mm"
		case watch_S6_44 			= "Apple Watch Series 6 - 44mm"
	}
}

Build your project again and Xcode should pick up the new ability to autocomplete any device when using the previewDevice() modifier in your SwiftUI code.


Updating for new devices

According to the Xcode 12.3 docs, you can run a command to see the available device types: xcrun simctl list devicetypes. Here's what I got as of Jan 11, 2021:

$ xcrun simctl list devicetypes
== Device Types ==
iPhone 4s (com.apple.CoreSimulator.SimDeviceType.iPhone-4s)
iPhone 5 (com.apple.CoreSimulator.SimDeviceType.iPhone-5)
iPhone 5s (com.apple.CoreSimulator.SimDeviceType.iPhone-5s)
iPhone 6 Plus (com.apple.CoreSimulator.SimDeviceType.iPhone-6-Plus)
iPhone 6 (com.apple.CoreSimulator.SimDeviceType.iPhone-6)
iPhone 6s (com.apple.CoreSimulator.SimDeviceType.iPhone-6s)
iPhone 6s Plus (com.apple.CoreSimulator.SimDeviceType.iPhone-6s-Plus)
iPhone SE (1st generation) (com.apple.CoreSimulator.SimDeviceType.iPhone-SE)
iPhone 7 (com.apple.CoreSimulator.SimDeviceType.iPhone-7)
iPhone 7 Plus (com.apple.CoreSimulator.SimDeviceType.iPhone-7-Plus)
iPhone 8 (com.apple.CoreSimulator.SimDeviceType.iPhone-8)
iPhone 8 Plus (com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus)
iPhone X (com.apple.CoreSimulator.SimDeviceType.iPhone-X)
iPhone Xs (com.apple.CoreSimulator.SimDeviceType.iPhone-XS)
iPhone Xs Max (com.apple.CoreSimulator.SimDeviceType.iPhone-XS-Max)
iPhone Xʀ (com.apple.CoreSimulator.SimDeviceType.iPhone-XR)
iPhone 11 (com.apple.CoreSimulator.SimDeviceType.iPhone-11)
iPhone 11 Pro (com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro)
iPhone 11 Pro Max (com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro-Max)
iPhone SE (2nd generation) (com.apple.CoreSimulator.SimDeviceType.iPhone-SE--2nd-generation-)
iPhone 12 mini (com.apple.CoreSimulator.SimDeviceType.iPhone-12-mini)
iPhone 12 (com.apple.CoreSimulator.SimDeviceType.iPhone-12)
iPhone 12 Pro (com.apple.CoreSimulator.SimDeviceType.iPhone-12-Pro)
iPhone 12 Pro Max (com.apple.CoreSimulator.SimDeviceType.iPhone-12-Pro-Max)
iPod touch (7th generation) (com.apple.CoreSimulator.SimDeviceType.iPod-touch--7th-generation-)
iPad 2 (com.apple.CoreSimulator.SimDeviceType.iPad-2)
iPad Retina (com.apple.CoreSimulator.SimDeviceType.iPad-Retina)
iPad Air (com.apple.CoreSimulator.SimDeviceType.iPad-Air)
iPad mini 2 (com.apple.CoreSimulator.SimDeviceType.iPad-mini-2)
iPad mini 3 (com.apple.CoreSimulator.SimDeviceType.iPad-mini-3)
iPad mini 4 (com.apple.CoreSimulator.SimDeviceType.iPad-mini-4)
iPad Air 2 (com.apple.CoreSimulator.SimDeviceType.iPad-Air-2)
iPad Pro (9.7-inch) (com.apple.CoreSimulator.SimDeviceType.iPad-Pro--9-7-inch-)
iPad Pro (12.9-inch) (1st generation) (com.apple.CoreSimulator.SimDeviceType.iPad-Pro)
iPad (5th generation) (com.apple.CoreSimulator.SimDeviceType.iPad--5th-generation-)
iPad Pro (12.9-inch) (2nd generation) (com.apple.CoreSimulator.SimDeviceType.iPad-Pro--12-9-inch---2nd-generation-)
iPad Pro (10.5-inch) (com.apple.CoreSimulator.SimDeviceType.iPad-Pro--10-5-inch-)
iPad (6th generation) (com.apple.CoreSimulator.SimDeviceType.iPad--6th-generation-)
iPad (7th generation) (com.apple.CoreSimulator.SimDeviceType.iPad--7th-generation-)
iPad Pro (11-inch) (1st generation) (com.apple.CoreSimulator.SimDeviceType.iPad-Pro--11-inch-)
iPad Pro (12.9-inch) (3rd generation) (com.apple.CoreSimulator.SimDeviceType.iPad-Pro--12-9-inch---3rd-generation-)
iPad Pro (11-inch) (2nd generation) (com.apple.CoreSimulator.SimDeviceType.iPad-Pro--11-inch---2nd-generation-)
iPad Pro (12.9-inch) (4th generation) (com.apple.CoreSimulator.SimDeviceType.iPad-Pro--12-9-inch---4th-generation-)
iPad mini (5th generation) (com.apple.CoreSimulator.SimDeviceType.iPad-mini--5th-generation-)
iPad Air (3rd generation) (com.apple.CoreSimulator.SimDeviceType.iPad-Air--3rd-generation-)
iPad (8th generation) (com.apple.CoreSimulator.SimDeviceType.iPad--8th-generation-)
iPad Air (4th generation) (com.apple.CoreSimulator.SimDeviceType.iPad-Air--4th-generation-)
Apple TV (com.apple.CoreSimulator.SimDeviceType.Apple-TV-1080p)
Apple TV 4K (com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-4K)
Apple TV 4K (at 1080p) (com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-1080p)
Apple Watch - 38mm (com.apple.CoreSimulator.SimDeviceType.Apple-Watch-38mm)
Apple Watch - 42mm (com.apple.CoreSimulator.SimDeviceType.Apple-Watch-42mm)
Apple Watch Series 2 - 38mm (com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-2-38mm)
Apple Watch Series 2 - 42mm (com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-2-42mm)
Apple Watch Series 3 - 38mm (com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-3-38mm)
Apple Watch Series 3 - 42mm (com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-3-42mm)
Apple Watch Series 4 - 40mm (com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-4-40mm)
Apple Watch Series 4 - 44mm (com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-4-44mm)
Apple Watch Series 5 - 40mm (com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-5-40mm)
Apple Watch Series 5 - 44mm (com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-5-44mm)
Apple Watch SE - 40mm (com.apple.CoreSimulator.SimDeviceType.Apple-Watch-SE-40mm)
Apple Watch SE - 44mm (com.apple.CoreSimulator.SimDeviceType.Apple-Watch-SE-44mm)
Apple Watch Series 6 - 40mm (com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-6-40mm)
Apple Watch Series 6 - 44mm (com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-6-44mm)

If any Apple engineers come across this, please augment the SwiftUI preview device modifier to accept non-string input.

This article is updated for Xcode 12.3 on Jan 11, 2021. Future SDK versions may add or remove devices, so plan to adjust this code as needed.