Skip to content

Instantly share code, notes, and snippets.

@codelynx
Created March 5, 2024 23:38
Show Gist options
  • Save codelynx/bebf5fd473a933fb98d1f9f741573dc4 to your computer and use it in GitHub Desktop.
Save codelynx/bebf5fd473a933fb98d1f9f741573dc4 to your computer and use it in GitHub Desktop.
Inspectables helps you implementing inspector view and its model in swift
//
// Inspectable.swift
// Inspectables
//
// Created by Kaz Yoshikawa on 3/5/24.
//
//
// Overview:
// Inspectables a set of code help you implementing inspectable property values and its inspector value editing visual components, by using
// @InspectableValue, or @InspectableEnum property wrappers, then you may forcus writing property value editing components.
//
// Discussion:
// This code helps implementing inspecter like component. For example model can be provided as follows.
//
// class SomeModel {
// @InspectableValue(name: "city") var city: String = ""
// @InspectableValue(name: "count") var count: Int = 0
// @InspectableValue(name: "color") var color: UIColor = .black
// }
//
// Then you may implement table view cell like this way. Be aware that you may still have to provide its own value editing user interface.
// But basically InspectablePropertyValue represents binding mechanism between model value and user interface. You may set and get value
// from InspectablePropertyValue<T>.
//
// class StringViewCell {
// var inspectableValue: InspectablePropertyValue<String>!
// init(inspectableValue: InspectableValueType) {
// guard let inspectableValue = inspectableValue as? InspectablePropertyValue<String> else { fatalError() }
// self.inspectableValue = inspectableValue
// }
// func setValue(value: String) {
// self.inspectableValue.value = value
// }
// }
//
// Then whewn you need to build a list of properties for table view like visyal component, then you can write this way, using '$' signatured
// projectedValue to bind between value in model and visual representation of inspector view.
//
// let stringCell = StringViewCell(inspectableValue: self.viewModel.$city)
// let integerCell = IntegerViewCell(inspectableValue: self.viewModel.$count)
// let colorCell = ColorViewCell(inspectableValue: self.viewModel.$color)
// let enumCell = EnumViewCell(inspectableValue: self.viewModel.$direction)
//
// When you need to implement enum values for inspector views, use @InspectableEnum, instead @InspectableValue. Then you do not have to provide inspector
// view for each enum declarations. You may still have to provide Popup menu or other visual represenation to show and pick enum value from inspector view,
// but as long as emum is CaseIterable, you will not have to provide inspector view for each enum types.
//
// @InspectableEnum(name: "direction") var direction: Direction = .north
//
// Then inspector view pseudo code may look like this, but InspectableEnumValueType can be givin by late initializer.
//
// class EnumViewCell {
// var inspectableValue: InspectableEnumValueType!
// init(inspectableValue: InspectableEnumValueType) {
// self.inspectableValue = inspectableValue
// }
// func setValue(named name: String) {
// inspectableValue.valueName = name
// }
// }
//
// Be aware, value is set by name which is represented by String(describing:). And cannot be directory set by value. If you still like to so. then
// You may have to write this way.
//
// inspectableValue.valueName = String(describing: Direction.south)
//
// Or, all name of values can be made from InspectableEnumValueType.names, and your inspector view may let user picked
// one of the value in string and set it with setValue(named:) method.
//
// let names = inspectableValue.names
// let name: String = names.randomElement()! // pretend picked one
// self.inspectableValue.setValue(named: name)
//
// Note that when you implemnting Inspector view as table view like component, you may have to write swich
#if os(macOS)
import AppKit
typealias XColor = NSColor
#elseif os(iOS)
import UIKit
typealias XColor = UIColor
#endif
protocol InspectableValueType {
}
@propertyWrapper
class InspectableValue<T> {
var name: String
var value: T
init(wrappedValue: T, name: String) {
self.name = name
self.value = wrappedValue
}
var wrappedValue: T {
get { return self.value }
set { self.value = newValue }
}
var projectedValue: InspectableValueType {
return InspectablePropertyValue<T>(getter: { self.value }, setter: { self.value = $0 })
}
}
@propertyWrapper
class InspectableEnum<T: CaseIterable & Equatable> {
var name: String
var value: T
init(wrappedValue: T, name: String) {
self.name = name
self.value = wrappedValue
}
var wrappedValue: T {
get { return self.value }
set { self.value = newValue }
}
var projectedValue: InspectableEnumValueType {
return InspectableEnumValue<T>(getter: { self.value }, setter: { self.value = $0 })
}
}
protocol InspectableEnumValueType: InspectableValueType {
var names: [String] { get }
var valueNamed: String { get set }
func setValue(named name: String)
}
class InspectablePropertyValue<T>: InspectableValueType {
let getter: (()->T)
let setter: ((T)->())
init(getter: @escaping (()->T), setter: @escaping ((T)->())) {
self.getter = getter
self.setter = setter
}
var value: T {
get { self.getter() }
set { self.setter(newValue) }
}
}
class InspectableEnumValue<T: CaseIterable & Equatable>: InspectablePropertyValue<T>, InspectableEnumValueType {
var names: [String] { T.allCases.map { String(describing: $0) } }
var valueNamed: String {
get {
let value: T = self.getter()
return String(describing: value)
}
set {
let value = T.allCases.filter { newValue == String(describing: $0) }.first!
self.setter(value)
}
}
func setValue(named name: String) {
self.valueNamed = name
}
}
/*
enum Direction: CaseIterable {
case north
case east
case south
case west
}
class StringViewCell {
var inspectableValue: InspectablePropertyValue<String>!
init(inspectableValue: InspectableValueType) {
guard let inspectableValue = inspectableValue as? InspectablePropertyValue<String> else { fatalError() }
self.inspectableValue = inspectableValue
}
func setValue(value: String) {
self.inspectableValue.value = value
}
}
class IntegerViewCell {
var inspectableValue: InspectablePropertyValue<Int>!
init(inspectableValue: InspectableValueType) {
guard let inspectableValue = inspectableValue as? InspectablePropertyValue<Int> else { fatalError() }
self.inspectableValue = inspectableValue
}
func setValue(value: Int) {
self.inspectableValue.value = value
}
}
class ColorViewCell {
var inspectableValue: InspectablePropertyValue<XColor>!
init(inspectableValue: InspectableValueType) {
guard let inspectableValue = inspectableValue as? InspectablePropertyValue<XColor> else { fatalError() }
self.inspectableValue = inspectableValue
}
func setValue(value: XColor) {
self.inspectableValue.value = value
}
}
class EnumViewCell {
var inspectableValue: InspectableEnumValueType!
init(inspectableValue: InspectableEnumValueType) {
self.inspectableValue = inspectableValue
}
func setValue(named name: String) {
self.inspectableValue.valueNamed = name
}
}
class SomeModel {
@InspectableValue(name: "city") var city: String = ""
@InspectableValue(name: "count") var count: Int = 0
@InspectableValue(name: "color") var color: XColor = .black
@InspectableEnum(name: "direction") var direction: Direction = .north
init(city: String, count: Int, color: XColor, direction: Direction) {
self.city = city
self.count = count
self.color = color
self.direction = direction
}
}
class Tester {
var viewModel = SomeModel(city: "Paris", count: 1, color: .black, direction: .north)
func test() {
let stringCell = StringViewCell(inspectableValue: self.viewModel.$city)
let integerCell = IntegerViewCell(inspectableValue: self.viewModel.$count)
let colorCell = ColorViewCell(inspectableValue: self.viewModel.$color)
let enumCell = EnumViewCell(inspectableValue: self.viewModel.$direction)
print("==before updating==")
print(viewModel.city)
print(viewModel.count)
print(viewModel.color)
print(viewModel.direction)
stringCell.setValue(value: "New York")
integerCell.setValue(value: 25)
colorCell.setValue(value: .red)
enumCell.setValue(named: String(describing: Direction.south))
print("==after updating==")
print(viewModel.city)
print(viewModel.count)
print(viewModel.color)
print(viewModel.direction)
}
}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment