Skip to content

Instantly share code, notes, and snippets.

@marcosgriselli
Created May 18, 2021 21:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marcosgriselli/eb4b6f076fcc440259f34f8bbcfd8209 to your computer and use it in GitHub Desktop.
Save marcosgriselli/eb4b6f076fcc440259f34f8bbcfd8209 to your computer and use it in GitHub Desktop.
Simulate side-by-side Xcode Previews
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
PreviewsPlus.Canvas(
content: ContentView(),
previews: [
Preview(device: .iPhoneSE, colorScheme: .light, contentSizeCategory: .extraExtraExtraLarge),
Preview(device: .iPhone12Pro, colorScheme: .dark),
Preview(device: .iPhone12Pro, contentSizeCategory: .accessibilityExtraLarge, layoutDirection: .rightToLeft),
Preview(device: .iPhone12Pro, colorScheme: .dark, contentSizeCategory: .extraSmall, layoutDirection: .rightToLeft)
]
)
}
}
struct Canvas<Content: View>: View {
var content: Content
var previews: [Preview]
init(content: Content, previews: [Preview]) {
self.content = content
self.previews = previews
}
var body: some View {
HStack(spacing: 32) {
ForEach(0..<previews.count) { index in
content
.ifLet(previews[index].colorScheme) {
$0.environment(\.colorScheme, $1)
}
.ifLet(previews[index].sizeCategory) {
$0.environment(\.sizeCategory, $1)
}
.ifLet(previews[index].layoutDirection) {
$0.environment(\.layoutDirection, $1)
}
.environment(\.horizontalSizeClass, previews[index].device.horizontalSizeClass)
.environment(\.verticalSizeClass, previews[index].device.verticalSizeClass)
.environment(\.displayScale, previews[index].device.displayScale)
.frame(width: previews[index].device.size.width, height: previews[index].device.size.height)
}
}
.previewLayout(.sizeThatFits)
}
}
struct Device {
let name: String
let size: CGSize
let horizontalSizeClass: UserInterfaceSizeClass
let verticalSizeClass: UserInterfaceSizeClass
let userInterfaceIdiom: UIUserInterfaceIdiom
let orientation: UIInterfaceOrientation
let safeArea: UIEdgeInsets
let displayScale: CGFloat
static let iPhoneSE = Device(
name: "iPhone SE (1st Gen)",
size: CGSize(width: 320, height: 568),
horizontalSizeClass: .compact,
verticalSizeClass: .regular,
userInterfaceIdiom: .phone,
orientation: .portrait,
safeArea: .init(top: 20, left: 0, bottom: 0, right: 0),
displayScale: 2
)
static let iPhone12Pro = Device(
name: "iPhone 12 Pro",
size: CGSize(width: 390, height: 844),
horizontalSizeClass: .compact,
verticalSizeClass: .regular,
userInterfaceIdiom: .phone,
orientation: .portrait,
safeArea: .init(top: 44, left: 0, bottom: 34, right: 0),
displayScale: 3
)
static let iPadPro12_9 = Device(
name: "iPad Pro 12.9-inch",
size: CGSize(width: 1024, height: 1366),
horizontalSizeClass: .regular,
verticalSizeClass: .regular,
userInterfaceIdiom: .pad,
orientation: .portrait,
safeArea: .init(top: 20, left: 0, bottom: 0, right: 0),
displayScale: 2
)
}
struct Preview {
let device: Device
let colorScheme: ColorScheme?
let sizeCategory: ContentSizeCategory?
let layoutDirection: LayoutDirection?
internal init(device: PreviewsPlus.Device, colorScheme: ColorScheme? = nil, contentSizeCategory: ContentSizeCategory? = nil, layoutDirection: LayoutDirection? = nil) {
self.device = device
self.colorScheme = colorScheme
self.sizeCategory = contentSizeCategory
self.layoutDirection = layoutDirection
}
}
extension View {
@ViewBuilder func ifLet<Content: View, T>(_ value: T?, transform: (Self, T) -> Content) -> some View {
if let value = value {
transform(self, value)
} else {
self
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment