👊 commit
This commit is contained in:
parent
5284b6ac55
commit
4f9ba07a1c
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"object": {
|
||||||
|
"pins": [
|
||||||
|
{
|
||||||
|
"package": "Chronicle",
|
||||||
|
"repositoryURL": "https://github.com/0xLeif/Chronicle",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "5d5e80f63943969eb2c33d686958b3938536a455",
|
||||||
|
"version": "0.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "SURL",
|
||||||
|
"repositoryURL": "https://github.com/0xLet/SURL",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "c04f941eea250f78f5c7151c03cd2489ca9ffc2b",
|
||||||
|
"version": "0.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "SwiftFu",
|
||||||
|
"repositoryURL": "https://github.com/0xLet/SwiftFu",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "818f889edc5a0c1657b50f3a1e841aa508177323",
|
||||||
|
"version": "1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "Task",
|
||||||
|
"repositoryURL": "https://github.com/0xLeif/Task",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "cfe31fd675b88c36194a34e8ccb8e8f8068d41c8",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "WTV",
|
||||||
|
"repositoryURL": "https://github.com/0xLet/WTV",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "8eefc65bf823eb7612047dbd2df4c6c73864c500",
|
||||||
|
"version": "0.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "Yarn",
|
||||||
|
"repositoryURL": "https://github.com/0xLeif/Yarn",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "3c50cc133bc71879aaf3bbb15fa271e9506489f8",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"version": 1
|
||||||
|
}
|
|
@ -5,6 +5,12 @@ import PackageDescription
|
||||||
|
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "ObjectUI",
|
name: "ObjectUI",
|
||||||
|
platforms: [
|
||||||
|
.iOS(.v13),
|
||||||
|
.macOS(.v10_15),
|
||||||
|
.watchOS(.v6),
|
||||||
|
.tvOS(.v13)
|
||||||
|
],
|
||||||
products: [
|
products: [
|
||||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||||
.library(
|
.library(
|
||||||
|
@ -14,13 +20,26 @@ let package = Package(
|
||||||
dependencies: [
|
dependencies: [
|
||||||
// Dependencies declare other packages that this package depends on.
|
// Dependencies declare other packages that this package depends on.
|
||||||
// .package(url: /* package url */, from: "1.0.0"),
|
// .package(url: /* package url */, from: "1.0.0"),
|
||||||
|
.package(url: "https://github.com/0xLet/WTV", from: "0.1.1"),
|
||||||
|
.package(url: "https://github.com/0xLet/SURL", from: "0.1.0"),
|
||||||
|
.package(url: "https://github.com/0xLet/SwiftFu", from: "1.0.1"),
|
||||||
|
.package(url: "https://github.com/0xLeif/Chronicle", from: "0.2.3"),
|
||||||
|
.package(url: "https://github.com/0xLeif/Task", from: "1.0.0"),
|
||||||
|
.package(url: "https://github.com/0xLeif/Yarn", from: "1.0.0")
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||||
.target(
|
.target(
|
||||||
name: "ObjectUI",
|
name: "ObjectUI",
|
||||||
dependencies: []),
|
dependencies: [
|
||||||
|
"WTV",
|
||||||
|
"SURL",
|
||||||
|
"SwiftFu",
|
||||||
|
"Chronicle",
|
||||||
|
"Task",
|
||||||
|
"Yarn"
|
||||||
|
]),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "ObjectUITests",
|
name: "ObjectUITests",
|
||||||
dependencies: ["ObjectUI"]),
|
dependencies: ["ObjectUI"]),
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# ObjectUI
|
# ObjectUI
|
||||||
|
|
||||||
A description of this package.
|
*Create SwiftUI Views with any data*
|
||||||
|
|
|
@ -0,0 +1,239 @@
|
||||||
|
//
|
||||||
|
// Object.swift
|
||||||
|
// ObjectUI
|
||||||
|
//
|
||||||
|
// Created by Leif on 5/24/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftFu
|
||||||
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
@dynamicMemberLookup
|
||||||
|
public class Object: FuableClass, ObservableObject {
|
||||||
|
public enum ObjectVariable: String, Hashable {
|
||||||
|
case value
|
||||||
|
case child
|
||||||
|
case array
|
||||||
|
case json
|
||||||
|
}
|
||||||
|
/// Variables of the object
|
||||||
|
@Published public var variables: [AnyHashable: Any] = [:]
|
||||||
|
/// @dynamicMemberLookup
|
||||||
|
public subscript(dynamicMember member: String) -> Object {
|
||||||
|
variable(named: member)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: public init
|
||||||
|
|
||||||
|
public init() { }
|
||||||
|
public convenience init(_ closure: (Object) -> Void) {
|
||||||
|
self.init()
|
||||||
|
|
||||||
|
closure(self)
|
||||||
|
}
|
||||||
|
public init(_ value: Any? = nil, _ closure: ((Object) -> Void)? = nil) {
|
||||||
|
defer {
|
||||||
|
if let closure = closure {
|
||||||
|
configure(closure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let value = value else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let unwrappedValue = unwrap(value)
|
||||||
|
if let _ = unwrappedValue as? NSNull {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let object = unwrappedValue as? Object {
|
||||||
|
consume(object)
|
||||||
|
} else if let array = unwrappedValue as? [Any] {
|
||||||
|
consume(Object(array: array))
|
||||||
|
} else if let dictionary = unwrappedValue as? [AnyHashable: Any] {
|
||||||
|
consume(Object(dictionary: dictionary))
|
||||||
|
} else if let data = unwrappedValue as? Data {
|
||||||
|
consume(Object(data: data))
|
||||||
|
} else {
|
||||||
|
consume(Object().set(value: unwrappedValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: private init
|
||||||
|
|
||||||
|
private init(array: [Any]) {
|
||||||
|
set(variable: ObjectVariable.array, value: array.map {
|
||||||
|
Object($0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
private init(dictionary: [AnyHashable: Any]) {
|
||||||
|
variables = dictionary
|
||||||
|
}
|
||||||
|
private init(data: Data) {
|
||||||
|
defer {
|
||||||
|
set(variable: ObjectVariable.json, value: String(data: data, encoding: .utf8))
|
||||||
|
set(value: data)
|
||||||
|
}
|
||||||
|
if let json = try? JSONSerialization.jsonObject(with: data,
|
||||||
|
options: .allowFragments) as? [Any] {
|
||||||
|
set(variable: ObjectVariable.array, value: json)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let json = try? JSONSerialization.jsonObject(with: data,
|
||||||
|
options: .allowFragments) as? [AnyHashable: Any] else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
consume(Object(json))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: public variables
|
||||||
|
|
||||||
|
|
||||||
|
public extension Object {
|
||||||
|
var array: [Object] {
|
||||||
|
if let array = variables[ObjectVariable.array] as? [Data] {
|
||||||
|
return array.map { Object(data: $0) }
|
||||||
|
} else if let array = variables[ObjectVariable.array] as? [Any] {
|
||||||
|
return array.map { value in
|
||||||
|
guard let json = value as? [AnyHashable: Any] else {
|
||||||
|
return Object(value)
|
||||||
|
}
|
||||||
|
return Object(dictionary: json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
var child: Object {
|
||||||
|
(variables[ObjectVariable.child] as? Object) ?? Object()
|
||||||
|
}
|
||||||
|
|
||||||
|
var value: Any {
|
||||||
|
variables[ObjectVariable.value] ?? Object()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: public functions
|
||||||
|
|
||||||
|
|
||||||
|
public extension Object {
|
||||||
|
/// Retrieve a Value from the current object
|
||||||
|
@discardableResult
|
||||||
|
func variable(named: AnyHashable) -> Object {
|
||||||
|
guard let value = variables[named] else {
|
||||||
|
return Object()
|
||||||
|
}
|
||||||
|
if let array = value as? [Any] {
|
||||||
|
return Object(array: array)
|
||||||
|
}
|
||||||
|
guard let object = value as? Object else {
|
||||||
|
return Object(unwrap(value))
|
||||||
|
}
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
/// Set a named Value to the current object
|
||||||
|
@discardableResult
|
||||||
|
func set(variable named: AnyHashable = ObjectVariable.value, value: Any?) -> Self {
|
||||||
|
guard let value = value,
|
||||||
|
(unwrap(value) as? NSNull) == nil else {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
variables[named] = value
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
/// Set a named Value to the current object
|
||||||
|
@discardableResult
|
||||||
|
func set<T>(variable named: T, value: Any?) -> Self where T: RawRepresentable, T.RawValue == String {
|
||||||
|
guard let value = value,
|
||||||
|
(unwrap(value) as? NSNull) == nil else {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
variables[named.rawValue] = value
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
/// Modify a Value with a name to the current object
|
||||||
|
@discardableResult
|
||||||
|
func modify<T>(variable named: AnyHashable = ObjectVariable.value, modifier: (T?) -> T?) -> Self {
|
||||||
|
guard let variable = variables[named],
|
||||||
|
let value = variable as? T else {
|
||||||
|
variables[named] = modifier(nil)
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
variables[named] = modifier(value)
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
/// Update a Value with a name to the current object
|
||||||
|
@discardableResult
|
||||||
|
func update<T>(variable named: AnyHashable = ObjectVariable.value, modifier: (T) -> T) -> Self {
|
||||||
|
guard let variable = variables[named],
|
||||||
|
let value = variable as? T else {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
variables[named] = modifier(value)
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
/// Set the ChildObject with a name of `_object` to the current object
|
||||||
|
@discardableResult
|
||||||
|
func set(childObject object: Object) -> Self {
|
||||||
|
variables[ObjectVariable.child] = object
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
/// Set the Array with a name of `_array` to the current object
|
||||||
|
@discardableResult
|
||||||
|
func set(array: [Any]) -> Self {
|
||||||
|
variables[ObjectVariable.array] = array
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func configure(_ closure: (Object) -> Void) -> Object {
|
||||||
|
closure(self)
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func consume(_ object: Object) -> Object {
|
||||||
|
object.variables.forEach { (key, value) in
|
||||||
|
self.set(variable: key, value: value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func value<T>(as type: T.Type? = nil) -> T? {
|
||||||
|
value as? T
|
||||||
|
}
|
||||||
|
|
||||||
|
func value<T>(decodedAs type: T.Type) -> T? where T: Decodable {
|
||||||
|
guard let data = value(as: Data.self) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return try? JSONDecoder().decode(T.self, from: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension Object {
|
||||||
|
/// Unwraps the <Optional> Any type
|
||||||
|
func unwrap(_ value: Any) -> Any {
|
||||||
|
let mValue = Mirror(reflecting: value)
|
||||||
|
let isValueOptional = mValue.displayStyle != .optional
|
||||||
|
let isValueEmpty = mValue.children.isEmpty
|
||||||
|
if isValueOptional { return value }
|
||||||
|
if isValueEmpty { return NSNull() }
|
||||||
|
guard let (_, unwrappedValue) = mValue.children.first else { return NSNull() }
|
||||||
|
return unwrappedValue
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +0,0 @@
|
||||||
struct ObjectUI {
|
|
||||||
var text = "Hello, World!"
|
|
||||||
}
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
// ObjectView.swift
|
||||||
|
// ObjectUI
|
||||||
|
//
|
||||||
|
// Created by Leif on 5/24/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public struct ObjectView<Content>: View where Content: View {
|
||||||
|
@ObservedObject private var object: Object
|
||||||
|
|
||||||
|
private var content: (Object) -> Content
|
||||||
|
|
||||||
|
public init(
|
||||||
|
data: Any? = nil,
|
||||||
|
content: @escaping (Object) -> Content
|
||||||
|
) {
|
||||||
|
self.object = Object(data)
|
||||||
|
self.content = content
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
content(object)
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,9 +3,11 @@
|
||||||
|
|
||||||
final class ObjectUITests: XCTestCase {
|
final class ObjectUITests: XCTestCase {
|
||||||
func testExample() {
|
func testExample() {
|
||||||
// This is an example of a functional test case.
|
enum Value: String {
|
||||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
case text
|
||||||
// results.
|
}
|
||||||
XCTAssertEqual(ObjectUI().text, "Hello, World!")
|
let object = Object()
|
||||||
|
object.set(variable: Value.text, value: "Hello, World!")
|
||||||
|
XCTAssertEqual(object.text.value(), "Hello, World!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue