Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Generate strongly typed PreviewDevice for SwiftUI
//
// main.swift
// PreviewDeviceExporter
//
// Created by Dan Tavares on 13/05/2021.
// https://github.com/JohnSundell/Files
// https://github.com/JohnSundell/ShellOut.git
// Packages used
import Foundation
import Files
import ShellOut
let template =
"""
//
// PreviewDeviceExporter
//
// Created by Daniel Tavares on 13/05/2021.
// http://twitter.com/vibrazy
// https://github.com/vibrazy
// http://dev.to/vibrazy
import SwiftUI
extension PreviewDevice {
%@
}
struct PreviewDeviceModifier: ViewModifier {
let previewDevice: PreviewDevice
func body(content: Content) -> some View {
content
.previewDevice(previewDevice)
.previewDisplayName(previewDevice.rawValue)
}
}
public extension View {
func preview(on previewDevice: PreviewDevice) -> some View {
self.modifier(
PreviewDeviceModifier(
previewDevice: previewDevice
)
)
}
}
"""
enum PreviewDeviceExporterError: Error {
case faieldToRetriveDevices
}
do {
let output = try shellOut(to: "xcrun simctl list --json devices available")
guard let data = output.data(using: .utf8) else {
throw PreviewDeviceExporterError.faieldToRetriveDevices
}
let deviceTypes = try JSONDecoder().decode(SimCTL.self, from: data)
let finalOutput = String(format: template, "\(deviceTypes)")
let folder = Folder.current
let file = try folder.createFile(named: "PreviewDeviceUtils.swift")
try file.write(finalOutput)
print("PreviewDeviceUtils.swift generated successfully ✅")
} catch let error {
print("Error occured: \(error)")
}
// MARK: - String Interpolation
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: SimCTL) {
let devicesString = value
.supportedDevices
.map({ "static let \($0.name.camelized) = Self(rawValue: \"\($0.name)\")" })
.joined(separator: "\n\t")
appendLiteral(devicesString)
}
}
// MARK: - String Utils
// https://gist.github.com/reitzig/67b41e75176ddfd432cb09392a270218
extension String {
var uppercasingFirst: String {
return prefix(1).uppercased() + dropFirst()
}
var lowercasingFirst: String {
return prefix(1).lowercased() + dropFirst()
}
var camelized: String {
guard !isEmpty else {
return ""
}
let parts = self.components(separatedBy: CharacterSet.alphanumerics.inverted)
let first = String(describing: parts.first!).lowercasingFirst
let rest = parts.dropFirst().map({String($0).uppercasingFirst})
return ([first] + rest).joined(separator: "")
}
}
// MARK: - Models
public struct SimCTL: Codable, Equatable {
private var devices: [String: [Device]]
public var supportedDevices: [Device] = []
enum CodingKeys: String, CodingKey {
case devices = "devices"
}
public init(devices: [String: [Device]]) {
self.devices = devices
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
devices = try container.decode([String: [Device]].self, forKey: CodingKeys.devices)
supportedDevices = devices
.filter({ !$0.value.isEmpty })
.map({ $0.value })
.flatMap({ $0 })
}
}
// MARK: - Device
public struct Device: Codable, Equatable {
public let dataPath: String
public let logPath: String
public let udid: String
public let isAvailable: Bool
public let deviceTypeIdentifier: String
public let state: State
public let name: String
enum CodingKeys: String, CodingKey {
case dataPath = "dataPath"
case logPath = "logPath"
case udid = "udid"
case isAvailable = "isAvailable"
case deviceTypeIdentifier = "deviceTypeIdentifier"
case state = "state"
case name = "name"
}
public init(dataPath: String, logPath: String, udid: String, isAvailable: Bool, deviceTypeIdentifier: String, state: State, name: String) {
self.dataPath = dataPath
self.logPath = logPath
self.udid = udid
self.isAvailable = isAvailable
self.deviceTypeIdentifier = deviceTypeIdentifier
self.state = state
self.name = name
}
}
public enum State: String, Codable, Equatable {
case booted = "Booted"
case shutdown = "Shutdown"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment