├── .gitignore
├── .swiftlint.yml
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ └── xcschemes
│ └── EditValueView.xcscheme
├── EditValueView.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ └── swiftpm
│ └── Package.resolved
├── Example
├── .swiftpm
│ └── xcode
│ │ └── package.xcworkspace
│ │ └── contents.xcworkspacedata
├── Example.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
├── Example
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── ContentView.swift
│ ├── Example.entitlements
│ ├── ExampleApp.swift
│ ├── Info.plist
│ └── Preview Content
│ │ └── Preview Assets.xcassets
│ │ └── Contents.json
├── ExampleTests
│ └── ExampleTests.swift
├── ExampleUITests
│ ├── ExampleUITests.swift
│ └── ExampleUITestsLaunchTests.swift
└── Package.swift
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
└── EditValueView
│ ├── EditValueView+EditorType.swift
│ ├── EditValueView+setValue.swift
│ ├── EditValueView.swift
│ ├── Editors
│ ├── AnyJSONEditor.swift
│ ├── CaseIterableEditor.swift
│ ├── CodableEditorView.swift
│ ├── ColorEditorView.swift
│ ├── DateEditorView.swift
│ └── Image
│ │ ├── ImageEditor.swift
│ │ ├── Picker
│ │ ├── ImagePicker.swift
│ │ └── SwiftUIImagePicker.swift
│ │ └── SwiftUIImageEditor.swift
│ ├── Extensions
│ ├── CIImage+.swift
│ ├── Codable+.swift
│ ├── UIImage+.swift
│ └── View+.swift
│ ├── Protocol
│ ├── DefaultRepresentable.swift
│ ├── OptionalCaseIterable.swift
│ ├── OptionalNumeric.swift
│ └── OptionalType.swift
│ ├── UIKit
│ └── EditValueViewController.swift
│ └── Util
│ └── Typealias.swift
└── Tests
└── EditValueViewTests
└── EditValueViewTests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | xcuserdata/
5 | DerivedData/
6 | .swiftpm/config/registries.json
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 | .netrc
9 |
10 | ## Build generated
11 | build/
12 | DerivedData/
13 |
14 | ## Various settings
15 | *.pbxuser
16 | !default.pbxuser
17 | *.mode1v3
18 | !default.mode1v3
19 | *.mode2v3
20 | !default.mode2v3
21 | *.perspectivev3
22 | !default.perspectivev3
23 | xcuserdata/
24 | node_modules/
25 |
26 | ## Other
27 | *.moved-aside
28 | *.xccheckout
29 | *.xcscmblueprint
30 | *.DS_Store
31 |
32 | ## Obj-C/Swift specific
33 | *.hmap
34 | *.ipa
35 | *.dSYM.zip
36 | *.dSYM
37 | *.generated.swift
38 |
39 |
40 | Carthage/
41 | Pods/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 |
50 | # bundler
51 | vendor/bundle
52 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | #- block_based_kvo
3 | #- class_delegate_protocol
4 | #- closing_brace
5 | #- closure_parameter_position
6 | #- colon
7 | #- comma
8 | #- comment_spacing
9 | #- compiler_protocol_init
10 | #- computed_accessors_order
11 | #- control_statement
12 | #- custom_rules
13 | #- cyclomatic_complexity
14 | #- deployment_target
15 | #- discouraged_direct_init
16 | #- duplicate_enum_cases
17 | #- duplicate_imports
18 | #- dynamic_inline
19 | #- empty_enum_arguments
20 | #- empty_parameters
21 | #- empty_parentheses_with_trailing_closure
22 | #- file_length
23 | #- for_where
24 | - force_cast
25 | #- force_try
26 | #- function_body_length
27 | #- function_parameter_count
28 | #- generic_type_name
29 | #- identifier_name
30 | #- implicit_getter
31 | #- inclusive_language
32 | #- inert_defer
33 | #- is_disjoint
34 | #- large_tuple
35 | #- leading_whitespace
36 | #- legacy_cggeometry_functions
37 | #- legacy_constant
38 | #- legacy_constructor
39 | #- legacy_hashing
40 | #- legacy_nsgeometry_functions
41 | #- line_length
42 | #- mark
43 | #- multiple_closures_with_trailing_closure
44 | #- nesting
45 | #- no_fallthrough_only
46 | #- no_space_in_method_call
47 | #- notification_center_detachment
48 | #- nsobject_prefer_isequal
49 | #- opening_brace
50 | #- operator_whitespace
51 | #- orphaned_doc_comment
52 | #- private_over_fileprivate
53 | #- private_unit_test
54 | #- protocol_property_accessors_order
55 | #- reduce_boolean
56 | #- redundant_discardable_let
57 | #- redundant_objc_attribute
58 | #- redundant_optional_initialization
59 | #- redundant_set_access_control
60 | #- redundant_string_enum_value
61 | #- redundant_void_return
62 | #- return_arrow_whitespace
63 | #- shorthand_operator
64 | #- statement_position
65 | #- superfluous_disable_command
66 | #- switch_case_alignment
67 | #- syntactic_sugar
68 | #- todo
69 | #- trailing_comma
70 | #- trailing_newline
71 | #- trailing_semicolon
72 | #- trailing_whitespace
73 | #- type_body_length
74 | #- type_name
75 | #- unneeded_break_in_switch
76 | #- unused_capture_list
77 | #- unused_closure_parameter
78 | #- unused_control_flow_label
79 | #- unused_enumerated
80 | #- unused_optional_binding
81 | #- unused_setter_value
82 | #- valid_ibinspectable
83 | #- vertical_parameter_alignment
84 | #- vertical_whitespace
85 | #- void_return
86 | #- weak_delegate
87 | #- xctfail_message
88 |
89 |
90 | opt_in_rules:
91 | - anyobject_protocol
92 | - array_init
93 | - attributes
94 | - balanced_xctest_lifecycle
95 | - capture_variable
96 | - closure_body_length
97 | - closure_end_indentation
98 | - closure_spacing
99 | - collection_alignment
100 | #- conditional_returns_on_newline
101 | - contains_over_filter_count
102 | - contains_over_filter_is_empty
103 | - contains_over_first_not_nil
104 | - contains_over_range_nil_comparison
105 | - convenience_type
106 | - discarded_notification_center_observer
107 | - discouraged_assert
108 | #- discouraged_object_literal
109 | - discouraged_optional_boolean
110 | - discouraged_optional_collection
111 | - empty_collection_literal
112 | - empty_count
113 | - empty_string
114 | - empty_xctest_method
115 | - enum_case_associated_values_count
116 | - expiring_todo
117 | #- explicit_acl
118 | #- explicit_enum_raw_value
119 | - explicit_init
120 | #- explicit_self
121 | #- explicit_top_level_acl
122 | #- explicit_type_interface
123 | #- extension_access_modifier
124 | - fallthrough
125 | - fatal_error_message
126 | #- file_header
127 | - file_name
128 | - file_name_no_space
129 | - file_types_order
130 | - first_where
131 | - flatmap_over_map_reduce
132 | - force_unwrapping
133 | - function_default_parameter_at_end
134 | - ibinspectable_in_extension
135 | - identical_operands
136 | - implicit_return
137 | #- implicitly_unwrapped_optional
138 | #- indentation_width
139 | - joined_default_parameter
140 | - last_where
141 | - legacy_multiple
142 | #- legacy_objc_type
143 | - legacy_random
144 | - let_var_whitespace
145 | - literal_expression_end_indentation
146 | - lower_acl_than_parent
147 | - missing_docs
148 | - modifier_order
149 | #- multiline_arguments
150 | #- multiline_arguments_brackets
151 | #- multiline_function_chains
152 | #- multiline_literal_brackets
153 | #- multiline_parameters
154 | #- multiline_parameters_brackets
155 | - nimble_operator
156 | #- no_extension_access_modifier
157 | #- no_grouping_extension
158 | - nslocalizedstring_key
159 | - nslocalizedstring_require_bundle
160 | #- number_separator
161 | #- object_literal
162 | - operator_usage_whitespace
163 | - optional_enum_case_matching
164 | - overridden_super_call
165 | - override_in_extension
166 | - pattern_matching_keywords
167 | - prefer_nimble
168 | - prefer_self_type_over_type_of_self
169 | - prefer_zero_over_explicit_init
170 | #- prefixed_toplevel_constant
171 | - private_action
172 | - private_outlet
173 | - private_subject
174 | #- prohibited_interface_builder
175 | - prohibited_super_call
176 | - quick_discouraged_call
177 | - quick_discouraged_focused_test
178 | - quick_discouraged_pending_test
179 | - raw_value_for_camel_cased_codable_enum
180 | - reduce_into
181 | - redundant_nil_coalescing
182 | - redundant_type_annotation
183 | #- required_deinit
184 | - required_enum_case
185 | - single_test_class
186 | - sorted_first_last
187 | #- sorted_imports
188 | - static_operator
189 | - strict_fileprivate
190 | #- strong_iboutlet
191 | - switch_case_on_newline
192 | - test_case_accessibility
193 | - toggle_bool
194 | - trailing_closure
195 | - type_contents_order
196 | - unavailable_function
197 | - unneeded_parentheses_in_closure_argument
198 | - unowned_variable_capture
199 | - untyped_error_in_catch
200 | - unused_declaration
201 | - unused_import
202 | - vertical_parameter_alignment_on_call
203 | #- vertical_whitespace_between_cases
204 | #- vertical_whitespace_closing_braces
205 | #- vertical_whitespace_opening_braces
206 | - xct_specific_matcher
207 | - yoda_condition
208 |
209 | included:
210 |
211 | excluded:
212 | - Pods
213 | - Carthage
214 | - SourcePackages
215 | - Generated
216 | - BuildTools
217 |
218 | line_length:
219 | warning: 300
220 | error: 500
221 |
222 | identifier_name:
223 | min_length:
224 | warning: 1
225 | excluded:
226 | - id
227 | - URL
228 | - GlobalAPIKey
229 |
230 | file_name:
231 | excluded:
232 | - main.swift
233 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/EditValueView.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
54 |
55 |
61 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/EditValueView.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/EditValueView.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/EditValueView.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "swift-magic-mirror",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/p-x9/swift-magic-mirror.git",
7 | "state" : {
8 | "revision" : "390e248dd6727e17aeb3949c12bb83e6eac876d1",
9 | "version" : "0.2.0"
10 | }
11 | },
12 | {
13 | "identity" : "swiftui-reflection-view",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/p-x9/swiftui-reflection-view.git",
16 | "state" : {
17 | "revision" : "ce3b810ecfa1b2e308cbb5192ee4c8c43b1eefa8",
18 | "version" : "0.8.0"
19 | }
20 | },
21 | {
22 | "identity" : "swiftuicolor",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/p-x9/SwiftUIColor.git",
25 | "state" : {
26 | "revision" : "aa42f452698cc0f78dcba2c6544f4abf7724602f",
27 | "version" : "0.4.0"
28 | }
29 | }
30 | ],
31 | "version" : 2
32 | }
33 |
--------------------------------------------------------------------------------
/Example/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 56;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 42B1D5292A6FFBBC00C06C53 /* ExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42B1D5282A6FFBBC00C06C53 /* ExampleApp.swift */; };
11 | 42B1D52B2A6FFBBC00C06C53 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42B1D52A2A6FFBBC00C06C53 /* ContentView.swift */; };
12 | 42B1D52D2A6FFBBE00C06C53 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 42B1D52C2A6FFBBE00C06C53 /* Assets.xcassets */; };
13 | 42B1D5312A6FFBBE00C06C53 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 42B1D5302A6FFBBE00C06C53 /* Preview Assets.xcassets */; };
14 | 42B1D53B2A6FFBBF00C06C53 /* ExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42B1D53A2A6FFBBF00C06C53 /* ExampleTests.swift */; };
15 | 42B1D5452A6FFBBF00C06C53 /* ExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42B1D5442A6FFBBF00C06C53 /* ExampleUITests.swift */; };
16 | 42B1D5472A6FFBBF00C06C53 /* ExampleUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42B1D5462A6FFBBF00C06C53 /* ExampleUITestsLaunchTests.swift */; };
17 | 42B1D5572A6FFD8C00C06C53 /* EditValueView in Frameworks */ = {isa = PBXBuildFile; productRef = 42B1D5562A6FFD8C00C06C53 /* EditValueView */; };
18 | /* End PBXBuildFile section */
19 |
20 | /* Begin PBXContainerItemProxy section */
21 | 42B1D5372A6FFBBF00C06C53 /* PBXContainerItemProxy */ = {
22 | isa = PBXContainerItemProxy;
23 | containerPortal = 42B1D51D2A6FFBBC00C06C53 /* Project object */;
24 | proxyType = 1;
25 | remoteGlobalIDString = 42B1D5242A6FFBBC00C06C53;
26 | remoteInfo = Example;
27 | };
28 | 42B1D5412A6FFBBF00C06C53 /* PBXContainerItemProxy */ = {
29 | isa = PBXContainerItemProxy;
30 | containerPortal = 42B1D51D2A6FFBBC00C06C53 /* Project object */;
31 | proxyType = 1;
32 | remoteGlobalIDString = 42B1D5242A6FFBBC00C06C53;
33 | remoteInfo = Example;
34 | };
35 | /* End PBXContainerItemProxy section */
36 |
37 | /* Begin PBXFileReference section */
38 | 42B1D5252A6FFBBC00C06C53 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
39 | 42B1D5282A6FFBBC00C06C53 /* ExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleApp.swift; sourceTree = ""; };
40 | 42B1D52A2A6FFBBC00C06C53 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
41 | 42B1D52C2A6FFBBE00C06C53 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
42 | 42B1D52E2A6FFBBE00C06C53 /* Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Example.entitlements; sourceTree = ""; };
43 | 42B1D5302A6FFBBE00C06C53 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
44 | 42B1D5362A6FFBBF00C06C53 /* ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
45 | 42B1D53A2A6FFBBF00C06C53 /* ExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleTests.swift; sourceTree = ""; };
46 | 42B1D5402A6FFBBF00C06C53 /* ExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
47 | 42B1D5442A6FFBBF00C06C53 /* ExampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleUITests.swift; sourceTree = ""; };
48 | 42B1D5462A6FFBBF00C06C53 /* ExampleUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleUITestsLaunchTests.swift; sourceTree = ""; };
49 | 42B1D5582A70DF9100C06C53 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; };
50 | 42B1D5592A743AC200C06C53 /* EditValueView */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = EditValueView; path = ..; sourceTree = ""; };
51 | /* End PBXFileReference section */
52 |
53 | /* Begin PBXFrameworksBuildPhase section */
54 | 42B1D5222A6FFBBC00C06C53 /* Frameworks */ = {
55 | isa = PBXFrameworksBuildPhase;
56 | buildActionMask = 2147483647;
57 | files = (
58 | 42B1D5572A6FFD8C00C06C53 /* EditValueView in Frameworks */,
59 | );
60 | runOnlyForDeploymentPostprocessing = 0;
61 | };
62 | 42B1D5332A6FFBBF00C06C53 /* Frameworks */ = {
63 | isa = PBXFrameworksBuildPhase;
64 | buildActionMask = 2147483647;
65 | files = (
66 | );
67 | runOnlyForDeploymentPostprocessing = 0;
68 | };
69 | 42B1D53D2A6FFBBF00C06C53 /* Frameworks */ = {
70 | isa = PBXFrameworksBuildPhase;
71 | buildActionMask = 2147483647;
72 | files = (
73 | );
74 | runOnlyForDeploymentPostprocessing = 0;
75 | };
76 | /* End PBXFrameworksBuildPhase section */
77 |
78 | /* Begin PBXGroup section */
79 | 42B1D51C2A6FFBBC00C06C53 = {
80 | isa = PBXGroup;
81 | children = (
82 | 42B1D5532A6FFBF400C06C53 /* Packages */,
83 | 42B1D5272A6FFBBC00C06C53 /* Example */,
84 | 42B1D5392A6FFBBF00C06C53 /* ExampleTests */,
85 | 42B1D5432A6FFBBF00C06C53 /* ExampleUITests */,
86 | 42B1D5262A6FFBBC00C06C53 /* Products */,
87 | 42B1D5552A6FFD8C00C06C53 /* Frameworks */,
88 | );
89 | sourceTree = "";
90 | };
91 | 42B1D5262A6FFBBC00C06C53 /* Products */ = {
92 | isa = PBXGroup;
93 | children = (
94 | 42B1D5252A6FFBBC00C06C53 /* Example.app */,
95 | 42B1D5362A6FFBBF00C06C53 /* ExampleTests.xctest */,
96 | 42B1D5402A6FFBBF00C06C53 /* ExampleUITests.xctest */,
97 | );
98 | name = Products;
99 | sourceTree = "";
100 | };
101 | 42B1D5272A6FFBBC00C06C53 /* Example */ = {
102 | isa = PBXGroup;
103 | children = (
104 | 42B1D5582A70DF9100C06C53 /* Info.plist */,
105 | 42B1D5282A6FFBBC00C06C53 /* ExampleApp.swift */,
106 | 42B1D52A2A6FFBBC00C06C53 /* ContentView.swift */,
107 | 42B1D52C2A6FFBBE00C06C53 /* Assets.xcassets */,
108 | 42B1D52E2A6FFBBE00C06C53 /* Example.entitlements */,
109 | 42B1D52F2A6FFBBE00C06C53 /* Preview Content */,
110 | );
111 | path = Example;
112 | sourceTree = "";
113 | };
114 | 42B1D52F2A6FFBBE00C06C53 /* Preview Content */ = {
115 | isa = PBXGroup;
116 | children = (
117 | 42B1D5302A6FFBBE00C06C53 /* Preview Assets.xcassets */,
118 | );
119 | path = "Preview Content";
120 | sourceTree = "";
121 | };
122 | 42B1D5392A6FFBBF00C06C53 /* ExampleTests */ = {
123 | isa = PBXGroup;
124 | children = (
125 | 42B1D53A2A6FFBBF00C06C53 /* ExampleTests.swift */,
126 | );
127 | path = ExampleTests;
128 | sourceTree = "";
129 | };
130 | 42B1D5432A6FFBBF00C06C53 /* ExampleUITests */ = {
131 | isa = PBXGroup;
132 | children = (
133 | 42B1D5442A6FFBBF00C06C53 /* ExampleUITests.swift */,
134 | 42B1D5462A6FFBBF00C06C53 /* ExampleUITestsLaunchTests.swift */,
135 | );
136 | path = ExampleUITests;
137 | sourceTree = "";
138 | };
139 | 42B1D5532A6FFBF400C06C53 /* Packages */ = {
140 | isa = PBXGroup;
141 | children = (
142 | 42B1D5592A743AC200C06C53 /* EditValueView */,
143 | );
144 | name = Packages;
145 | sourceTree = "";
146 | };
147 | 42B1D5552A6FFD8C00C06C53 /* Frameworks */ = {
148 | isa = PBXGroup;
149 | children = (
150 | );
151 | name = Frameworks;
152 | sourceTree = "";
153 | };
154 | /* End PBXGroup section */
155 |
156 | /* Begin PBXNativeTarget section */
157 | 42B1D5242A6FFBBC00C06C53 /* Example */ = {
158 | isa = PBXNativeTarget;
159 | buildConfigurationList = 42B1D54A2A6FFBBF00C06C53 /* Build configuration list for PBXNativeTarget "Example" */;
160 | buildPhases = (
161 | 42B1D5212A6FFBBC00C06C53 /* Sources */,
162 | 42B1D5222A6FFBBC00C06C53 /* Frameworks */,
163 | 42B1D5232A6FFBBC00C06C53 /* Resources */,
164 | );
165 | buildRules = (
166 | );
167 | dependencies = (
168 | );
169 | name = Example;
170 | packageProductDependencies = (
171 | 42B1D5562A6FFD8C00C06C53 /* EditValueView */,
172 | );
173 | productName = Example;
174 | productReference = 42B1D5252A6FFBBC00C06C53 /* Example.app */;
175 | productType = "com.apple.product-type.application";
176 | };
177 | 42B1D5352A6FFBBF00C06C53 /* ExampleTests */ = {
178 | isa = PBXNativeTarget;
179 | buildConfigurationList = 42B1D54D2A6FFBBF00C06C53 /* Build configuration list for PBXNativeTarget "ExampleTests" */;
180 | buildPhases = (
181 | 42B1D5322A6FFBBF00C06C53 /* Sources */,
182 | 42B1D5332A6FFBBF00C06C53 /* Frameworks */,
183 | 42B1D5342A6FFBBF00C06C53 /* Resources */,
184 | );
185 | buildRules = (
186 | );
187 | dependencies = (
188 | 42B1D5382A6FFBBF00C06C53 /* PBXTargetDependency */,
189 | );
190 | name = ExampleTests;
191 | productName = ExampleTests;
192 | productReference = 42B1D5362A6FFBBF00C06C53 /* ExampleTests.xctest */;
193 | productType = "com.apple.product-type.bundle.unit-test";
194 | };
195 | 42B1D53F2A6FFBBF00C06C53 /* ExampleUITests */ = {
196 | isa = PBXNativeTarget;
197 | buildConfigurationList = 42B1D5502A6FFBBF00C06C53 /* Build configuration list for PBXNativeTarget "ExampleUITests" */;
198 | buildPhases = (
199 | 42B1D53C2A6FFBBF00C06C53 /* Sources */,
200 | 42B1D53D2A6FFBBF00C06C53 /* Frameworks */,
201 | 42B1D53E2A6FFBBF00C06C53 /* Resources */,
202 | );
203 | buildRules = (
204 | );
205 | dependencies = (
206 | 42B1D5422A6FFBBF00C06C53 /* PBXTargetDependency */,
207 | );
208 | name = ExampleUITests;
209 | productName = ExampleUITests;
210 | productReference = 42B1D5402A6FFBBF00C06C53 /* ExampleUITests.xctest */;
211 | productType = "com.apple.product-type.bundle.ui-testing";
212 | };
213 | /* End PBXNativeTarget section */
214 |
215 | /* Begin PBXProject section */
216 | 42B1D51D2A6FFBBC00C06C53 /* Project object */ = {
217 | isa = PBXProject;
218 | attributes = {
219 | BuildIndependentTargetsInParallel = 1;
220 | LastSwiftUpdateCheck = 1430;
221 | LastUpgradeCheck = 1430;
222 | TargetAttributes = {
223 | 42B1D5242A6FFBBC00C06C53 = {
224 | CreatedOnToolsVersion = 14.3.1;
225 | };
226 | 42B1D5352A6FFBBF00C06C53 = {
227 | CreatedOnToolsVersion = 14.3.1;
228 | TestTargetID = 42B1D5242A6FFBBC00C06C53;
229 | };
230 | 42B1D53F2A6FFBBF00C06C53 = {
231 | CreatedOnToolsVersion = 14.3.1;
232 | TestTargetID = 42B1D5242A6FFBBC00C06C53;
233 | };
234 | };
235 | };
236 | buildConfigurationList = 42B1D5202A6FFBBC00C06C53 /* Build configuration list for PBXProject "Example" */;
237 | compatibilityVersion = "Xcode 14.0";
238 | developmentRegion = en;
239 | hasScannedForEncodings = 0;
240 | knownRegions = (
241 | en,
242 | Base,
243 | );
244 | mainGroup = 42B1D51C2A6FFBBC00C06C53;
245 | productRefGroup = 42B1D5262A6FFBBC00C06C53 /* Products */;
246 | projectDirPath = "";
247 | projectRoot = "";
248 | targets = (
249 | 42B1D5242A6FFBBC00C06C53 /* Example */,
250 | 42B1D5352A6FFBBF00C06C53 /* ExampleTests */,
251 | 42B1D53F2A6FFBBF00C06C53 /* ExampleUITests */,
252 | );
253 | };
254 | /* End PBXProject section */
255 |
256 | /* Begin PBXResourcesBuildPhase section */
257 | 42B1D5232A6FFBBC00C06C53 /* Resources */ = {
258 | isa = PBXResourcesBuildPhase;
259 | buildActionMask = 2147483647;
260 | files = (
261 | 42B1D5312A6FFBBE00C06C53 /* Preview Assets.xcassets in Resources */,
262 | 42B1D52D2A6FFBBE00C06C53 /* Assets.xcassets in Resources */,
263 | );
264 | runOnlyForDeploymentPostprocessing = 0;
265 | };
266 | 42B1D5342A6FFBBF00C06C53 /* Resources */ = {
267 | isa = PBXResourcesBuildPhase;
268 | buildActionMask = 2147483647;
269 | files = (
270 | );
271 | runOnlyForDeploymentPostprocessing = 0;
272 | };
273 | 42B1D53E2A6FFBBF00C06C53 /* Resources */ = {
274 | isa = PBXResourcesBuildPhase;
275 | buildActionMask = 2147483647;
276 | files = (
277 | );
278 | runOnlyForDeploymentPostprocessing = 0;
279 | };
280 | /* End PBXResourcesBuildPhase section */
281 |
282 | /* Begin PBXSourcesBuildPhase section */
283 | 42B1D5212A6FFBBC00C06C53 /* Sources */ = {
284 | isa = PBXSourcesBuildPhase;
285 | buildActionMask = 2147483647;
286 | files = (
287 | 42B1D52B2A6FFBBC00C06C53 /* ContentView.swift in Sources */,
288 | 42B1D5292A6FFBBC00C06C53 /* ExampleApp.swift in Sources */,
289 | );
290 | runOnlyForDeploymentPostprocessing = 0;
291 | };
292 | 42B1D5322A6FFBBF00C06C53 /* Sources */ = {
293 | isa = PBXSourcesBuildPhase;
294 | buildActionMask = 2147483647;
295 | files = (
296 | 42B1D53B2A6FFBBF00C06C53 /* ExampleTests.swift in Sources */,
297 | );
298 | runOnlyForDeploymentPostprocessing = 0;
299 | };
300 | 42B1D53C2A6FFBBF00C06C53 /* Sources */ = {
301 | isa = PBXSourcesBuildPhase;
302 | buildActionMask = 2147483647;
303 | files = (
304 | 42B1D5452A6FFBBF00C06C53 /* ExampleUITests.swift in Sources */,
305 | 42B1D5472A6FFBBF00C06C53 /* ExampleUITestsLaunchTests.swift in Sources */,
306 | );
307 | runOnlyForDeploymentPostprocessing = 0;
308 | };
309 | /* End PBXSourcesBuildPhase section */
310 |
311 | /* Begin PBXTargetDependency section */
312 | 42B1D5382A6FFBBF00C06C53 /* PBXTargetDependency */ = {
313 | isa = PBXTargetDependency;
314 | target = 42B1D5242A6FFBBC00C06C53 /* Example */;
315 | targetProxy = 42B1D5372A6FFBBF00C06C53 /* PBXContainerItemProxy */;
316 | };
317 | 42B1D5422A6FFBBF00C06C53 /* PBXTargetDependency */ = {
318 | isa = PBXTargetDependency;
319 | target = 42B1D5242A6FFBBC00C06C53 /* Example */;
320 | targetProxy = 42B1D5412A6FFBBF00C06C53 /* PBXContainerItemProxy */;
321 | };
322 | /* End PBXTargetDependency section */
323 |
324 | /* Begin XCBuildConfiguration section */
325 | 42B1D5482A6FFBBF00C06C53 /* Debug */ = {
326 | isa = XCBuildConfiguration;
327 | buildSettings = {
328 | ALWAYS_SEARCH_USER_PATHS = NO;
329 | CLANG_ANALYZER_NONNULL = YES;
330 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
331 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
332 | CLANG_ENABLE_MODULES = YES;
333 | CLANG_ENABLE_OBJC_ARC = YES;
334 | CLANG_ENABLE_OBJC_WEAK = YES;
335 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
336 | CLANG_WARN_BOOL_CONVERSION = YES;
337 | CLANG_WARN_COMMA = YES;
338 | CLANG_WARN_CONSTANT_CONVERSION = YES;
339 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
340 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
341 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
342 | CLANG_WARN_EMPTY_BODY = YES;
343 | CLANG_WARN_ENUM_CONVERSION = YES;
344 | CLANG_WARN_INFINITE_RECURSION = YES;
345 | CLANG_WARN_INT_CONVERSION = YES;
346 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
347 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
348 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
349 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
350 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
351 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
352 | CLANG_WARN_STRICT_PROTOTYPES = YES;
353 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
354 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
355 | CLANG_WARN_UNREACHABLE_CODE = YES;
356 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
357 | COPY_PHASE_STRIP = NO;
358 | DEBUG_INFORMATION_FORMAT = dwarf;
359 | ENABLE_STRICT_OBJC_MSGSEND = YES;
360 | ENABLE_TESTABILITY = YES;
361 | GCC_C_LANGUAGE_STANDARD = gnu11;
362 | GCC_DYNAMIC_NO_PIC = NO;
363 | GCC_NO_COMMON_BLOCKS = YES;
364 | GCC_OPTIMIZATION_LEVEL = 0;
365 | GCC_PREPROCESSOR_DEFINITIONS = (
366 | "DEBUG=1",
367 | "$(inherited)",
368 | );
369 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
370 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
371 | GCC_WARN_UNDECLARED_SELECTOR = YES;
372 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
373 | GCC_WARN_UNUSED_FUNCTION = YES;
374 | GCC_WARN_UNUSED_VARIABLE = YES;
375 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
376 | MTL_FAST_MATH = YES;
377 | ONLY_ACTIVE_ARCH = YES;
378 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
379 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
380 | };
381 | name = Debug;
382 | };
383 | 42B1D5492A6FFBBF00C06C53 /* Release */ = {
384 | isa = XCBuildConfiguration;
385 | buildSettings = {
386 | ALWAYS_SEARCH_USER_PATHS = NO;
387 | CLANG_ANALYZER_NONNULL = YES;
388 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
389 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
390 | CLANG_ENABLE_MODULES = YES;
391 | CLANG_ENABLE_OBJC_ARC = YES;
392 | CLANG_ENABLE_OBJC_WEAK = YES;
393 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
394 | CLANG_WARN_BOOL_CONVERSION = YES;
395 | CLANG_WARN_COMMA = YES;
396 | CLANG_WARN_CONSTANT_CONVERSION = YES;
397 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
398 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
399 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
400 | CLANG_WARN_EMPTY_BODY = YES;
401 | CLANG_WARN_ENUM_CONVERSION = YES;
402 | CLANG_WARN_INFINITE_RECURSION = YES;
403 | CLANG_WARN_INT_CONVERSION = YES;
404 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
405 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
406 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
407 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
408 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
409 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
410 | CLANG_WARN_STRICT_PROTOTYPES = YES;
411 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
412 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
413 | CLANG_WARN_UNREACHABLE_CODE = YES;
414 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
415 | COPY_PHASE_STRIP = NO;
416 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
417 | ENABLE_NS_ASSERTIONS = NO;
418 | ENABLE_STRICT_OBJC_MSGSEND = YES;
419 | GCC_C_LANGUAGE_STANDARD = gnu11;
420 | GCC_NO_COMMON_BLOCKS = YES;
421 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
422 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
423 | GCC_WARN_UNDECLARED_SELECTOR = YES;
424 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
425 | GCC_WARN_UNUSED_FUNCTION = YES;
426 | GCC_WARN_UNUSED_VARIABLE = YES;
427 | MTL_ENABLE_DEBUG_INFO = NO;
428 | MTL_FAST_MATH = YES;
429 | SWIFT_COMPILATION_MODE = wholemodule;
430 | SWIFT_OPTIMIZATION_LEVEL = "-O";
431 | };
432 | name = Release;
433 | };
434 | 42B1D54B2A6FFBBF00C06C53 /* Debug */ = {
435 | isa = XCBuildConfiguration;
436 | buildSettings = {
437 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
438 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
439 | CODE_SIGN_ENTITLEMENTS = Example/Example.entitlements;
440 | CODE_SIGN_STYLE = Automatic;
441 | CURRENT_PROJECT_VERSION = 1;
442 | DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\"";
443 | DEVELOPMENT_TEAM = WLCQDVKTS9;
444 | ENABLE_HARDENED_RUNTIME = YES;
445 | ENABLE_PREVIEWS = YES;
446 | GENERATE_INFOPLIST_FILE = YES;
447 | INFOPLIST_FILE = Example/Info.plist;
448 | INFOPLIST_KEY_NSCameraUsageDescription = "To select image";
449 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
450 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
451 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
452 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
453 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
454 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
455 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
456 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
457 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
458 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
459 | IPHONEOS_DEPLOYMENT_TARGET = 16.4;
460 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
461 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
462 | MACOSX_DEPLOYMENT_TARGET = 13.3;
463 | MARKETING_VERSION = 1.0;
464 | PRODUCT_BUNDLE_IDENTIFIER = "com.p-x9.Example";
465 | PRODUCT_NAME = "$(TARGET_NAME)";
466 | SDKROOT = auto;
467 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
468 | SWIFT_EMIT_LOC_STRINGS = YES;
469 | SWIFT_VERSION = 5.0;
470 | TARGETED_DEVICE_FAMILY = "1,2";
471 | };
472 | name = Debug;
473 | };
474 | 42B1D54C2A6FFBBF00C06C53 /* Release */ = {
475 | isa = XCBuildConfiguration;
476 | buildSettings = {
477 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
478 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
479 | CODE_SIGN_ENTITLEMENTS = Example/Example.entitlements;
480 | CODE_SIGN_STYLE = Automatic;
481 | CURRENT_PROJECT_VERSION = 1;
482 | DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\"";
483 | DEVELOPMENT_TEAM = WLCQDVKTS9;
484 | ENABLE_HARDENED_RUNTIME = YES;
485 | ENABLE_PREVIEWS = YES;
486 | GENERATE_INFOPLIST_FILE = YES;
487 | INFOPLIST_FILE = Example/Info.plist;
488 | INFOPLIST_KEY_NSCameraUsageDescription = "To select image";
489 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
490 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
491 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
492 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
493 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
494 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
495 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
496 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
497 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
498 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
499 | IPHONEOS_DEPLOYMENT_TARGET = 16.4;
500 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
501 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
502 | MACOSX_DEPLOYMENT_TARGET = 13.3;
503 | MARKETING_VERSION = 1.0;
504 | PRODUCT_BUNDLE_IDENTIFIER = "com.p-x9.Example";
505 | PRODUCT_NAME = "$(TARGET_NAME)";
506 | SDKROOT = auto;
507 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
508 | SWIFT_EMIT_LOC_STRINGS = YES;
509 | SWIFT_VERSION = 5.0;
510 | TARGETED_DEVICE_FAMILY = "1,2";
511 | };
512 | name = Release;
513 | };
514 | 42B1D54E2A6FFBBF00C06C53 /* Debug */ = {
515 | isa = XCBuildConfiguration;
516 | buildSettings = {
517 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
518 | BUNDLE_LOADER = "$(TEST_HOST)";
519 | CODE_SIGN_STYLE = Automatic;
520 | CURRENT_PROJECT_VERSION = 1;
521 | DEVELOPMENT_TEAM = WLCQDVKTS9;
522 | GENERATE_INFOPLIST_FILE = YES;
523 | IPHONEOS_DEPLOYMENT_TARGET = 16.4;
524 | MACOSX_DEPLOYMENT_TARGET = 13.3;
525 | MARKETING_VERSION = 1.0;
526 | PRODUCT_BUNDLE_IDENTIFIER = "com.p-x9.ExampleTests";
527 | PRODUCT_NAME = "$(TARGET_NAME)";
528 | SDKROOT = auto;
529 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
530 | SWIFT_EMIT_LOC_STRINGS = NO;
531 | SWIFT_VERSION = 5.0;
532 | TARGETED_DEVICE_FAMILY = "1,2";
533 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Example";
534 | };
535 | name = Debug;
536 | };
537 | 42B1D54F2A6FFBBF00C06C53 /* Release */ = {
538 | isa = XCBuildConfiguration;
539 | buildSettings = {
540 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
541 | BUNDLE_LOADER = "$(TEST_HOST)";
542 | CODE_SIGN_STYLE = Automatic;
543 | CURRENT_PROJECT_VERSION = 1;
544 | DEVELOPMENT_TEAM = WLCQDVKTS9;
545 | GENERATE_INFOPLIST_FILE = YES;
546 | IPHONEOS_DEPLOYMENT_TARGET = 16.4;
547 | MACOSX_DEPLOYMENT_TARGET = 13.3;
548 | MARKETING_VERSION = 1.0;
549 | PRODUCT_BUNDLE_IDENTIFIER = "com.p-x9.ExampleTests";
550 | PRODUCT_NAME = "$(TARGET_NAME)";
551 | SDKROOT = auto;
552 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
553 | SWIFT_EMIT_LOC_STRINGS = NO;
554 | SWIFT_VERSION = 5.0;
555 | TARGETED_DEVICE_FAMILY = "1,2";
556 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Example";
557 | };
558 | name = Release;
559 | };
560 | 42B1D5512A6FFBBF00C06C53 /* Debug */ = {
561 | isa = XCBuildConfiguration;
562 | buildSettings = {
563 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
564 | CODE_SIGN_STYLE = Automatic;
565 | CURRENT_PROJECT_VERSION = 1;
566 | DEVELOPMENT_TEAM = WLCQDVKTS9;
567 | GENERATE_INFOPLIST_FILE = YES;
568 | IPHONEOS_DEPLOYMENT_TARGET = 16.4;
569 | MACOSX_DEPLOYMENT_TARGET = 13.3;
570 | MARKETING_VERSION = 1.0;
571 | PRODUCT_BUNDLE_IDENTIFIER = "com.p-x9.ExampleUITests";
572 | PRODUCT_NAME = "$(TARGET_NAME)";
573 | SDKROOT = auto;
574 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
575 | SWIFT_EMIT_LOC_STRINGS = NO;
576 | SWIFT_VERSION = 5.0;
577 | TARGETED_DEVICE_FAMILY = "1,2";
578 | TEST_TARGET_NAME = Example;
579 | };
580 | name = Debug;
581 | };
582 | 42B1D5522A6FFBBF00C06C53 /* Release */ = {
583 | isa = XCBuildConfiguration;
584 | buildSettings = {
585 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
586 | CODE_SIGN_STYLE = Automatic;
587 | CURRENT_PROJECT_VERSION = 1;
588 | DEVELOPMENT_TEAM = WLCQDVKTS9;
589 | GENERATE_INFOPLIST_FILE = YES;
590 | IPHONEOS_DEPLOYMENT_TARGET = 16.4;
591 | MACOSX_DEPLOYMENT_TARGET = 13.3;
592 | MARKETING_VERSION = 1.0;
593 | PRODUCT_BUNDLE_IDENTIFIER = "com.p-x9.ExampleUITests";
594 | PRODUCT_NAME = "$(TARGET_NAME)";
595 | SDKROOT = auto;
596 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
597 | SWIFT_EMIT_LOC_STRINGS = NO;
598 | SWIFT_VERSION = 5.0;
599 | TARGETED_DEVICE_FAMILY = "1,2";
600 | TEST_TARGET_NAME = Example;
601 | };
602 | name = Release;
603 | };
604 | /* End XCBuildConfiguration section */
605 |
606 | /* Begin XCConfigurationList section */
607 | 42B1D5202A6FFBBC00C06C53 /* Build configuration list for PBXProject "Example" */ = {
608 | isa = XCConfigurationList;
609 | buildConfigurations = (
610 | 42B1D5482A6FFBBF00C06C53 /* Debug */,
611 | 42B1D5492A6FFBBF00C06C53 /* Release */,
612 | );
613 | defaultConfigurationIsVisible = 0;
614 | defaultConfigurationName = Release;
615 | };
616 | 42B1D54A2A6FFBBF00C06C53 /* Build configuration list for PBXNativeTarget "Example" */ = {
617 | isa = XCConfigurationList;
618 | buildConfigurations = (
619 | 42B1D54B2A6FFBBF00C06C53 /* Debug */,
620 | 42B1D54C2A6FFBBF00C06C53 /* Release */,
621 | );
622 | defaultConfigurationIsVisible = 0;
623 | defaultConfigurationName = Release;
624 | };
625 | 42B1D54D2A6FFBBF00C06C53 /* Build configuration list for PBXNativeTarget "ExampleTests" */ = {
626 | isa = XCConfigurationList;
627 | buildConfigurations = (
628 | 42B1D54E2A6FFBBF00C06C53 /* Debug */,
629 | 42B1D54F2A6FFBBF00C06C53 /* Release */,
630 | );
631 | defaultConfigurationIsVisible = 0;
632 | defaultConfigurationName = Release;
633 | };
634 | 42B1D5502A6FFBBF00C06C53 /* Build configuration list for PBXNativeTarget "ExampleUITests" */ = {
635 | isa = XCConfigurationList;
636 | buildConfigurations = (
637 | 42B1D5512A6FFBBF00C06C53 /* Debug */,
638 | 42B1D5522A6FFBBF00C06C53 /* Release */,
639 | );
640 | defaultConfigurationIsVisible = 0;
641 | defaultConfigurationName = Release;
642 | };
643 | /* End XCConfigurationList section */
644 |
645 | /* Begin XCSwiftPackageProductDependency section */
646 | 42B1D5562A6FFD8C00C06C53 /* EditValueView */ = {
647 | isa = XCSwiftPackageProductDependency;
648 | productName = EditValueView;
649 | };
650 | /* End XCSwiftPackageProductDependency section */
651 | };
652 | rootObject = 42B1D51D2A6FFBBC00C06C53 /* Project object */;
653 | }
654 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "swiftuicolor",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/p-x9/SwiftUIColor.git",
7 | "state" : {
8 | "revision" : "61238f7460a04314dc059df68a1aa4c4b7dcb5df",
9 | "version" : "0.3.0"
10 | }
11 | }
12 | ],
13 | "version" : 2
14 | }
15 |
--------------------------------------------------------------------------------
/Example/Example/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "1x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "2x",
16 | "size" : "16x16"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "1x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "2x",
26 | "size" : "32x32"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "2x",
36 | "size" : "128x128"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "1x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "2x",
46 | "size" : "256x256"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "1x",
51 | "size" : "512x512"
52 | },
53 | {
54 | "idiom" : "mac",
55 | "scale" : "2x",
56 | "size" : "512x512"
57 | }
58 | ],
59 | "info" : {
60 | "author" : "xcode",
61 | "version" : 1
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Example/Example/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Example/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // Example
4 | //
5 | // Created by p-x9 on 2023/07/25.
6 | //
7 | //
8 |
9 | import SwiftUI
10 | import EditValueView
11 | import UIKit
12 |
13 | struct ContentView: View {
14 | @State var target: Item = .init(name: "text",
15 | bool: false,
16 | date: Date(),
17 | enum: .red,
18 | enum2: .blue,
19 | color: .white)
20 |
21 | var body: some View {
22 | NavigationView {
23 | List {
24 | standardType
25 | collectionType
26 | codableType
27 | colorType
28 | imageType
29 | notSupportedType
30 | }
31 | .navigationTitle(Text("EditValueView"))
32 | }
33 | }
34 |
35 | var standardType: some View {
36 | Section {
37 | editValueView(title: "String", key: "name", keyPath: \.name)
38 |
39 | editValueView(title: "Bool", key: "bool", keyPath: \.bool)
40 |
41 | editValueView(title: "Date", key: "date", keyPath: \.date)
42 |
43 | NavigationLink("Int", destination: {
44 | EditValueView(
45 | target,
46 | key: "number",
47 | keyPath: \Item.codable.number,
48 | presentationStyle: .push
49 | )
50 | .onUpdate { newValue in
51 | target.codable.number = newValue
52 | }
53 | .validate { newValue in
54 | newValue.isMultiple(of: 2)
55 | }
56 | })
57 |
58 | NavigationLink("Double") {
59 | EditValueView(
60 | target,
61 | key: "double",
62 | keyPath: \Item.codable.double,
63 | presentationStyle: .push
64 | )
65 | .onUpdate { newValue in
66 | target.codable.double = newValue
67 | }
68 | .validate { newValue in
69 | newValue > 0
70 | }
71 | }
72 | } header: {
73 | Text("Standard")
74 | }
75 | }
76 |
77 | var collectionType: some View {
78 | Section {
79 | editValueView(title: "CaseIterable", key: "enum", keyPath: \.enum)
80 |
81 | editValueView(title: "CaseIterable & RawRepresentable", key: "enum2", keyPath: \.enum2)
82 |
83 | editValueView(title: "Array", key: "array", keyPath: \.array)
84 |
85 | editValueView(title: "Dictionary", key: "dictionary", keyPath: \.dictionary)
86 | editValueView(title: "Any Dictionary", key: "anyDictionary", keyPath: \.anyDictionary)
87 | } header: {
88 | Text("Collection")
89 | }
90 | }
91 |
92 | var codableType: some View {
93 | Section {
94 | editValueView(title: "Codable", key: "codable", keyPath: \.codable)
95 | } header: {
96 | Text("Codable")
97 | }
98 | }
99 |
100 | var colorType: some View {
101 | Section {
102 | editValueView(title: "Color", key: "color", keyPath: \.color)
103 |
104 | editValueView(title: "UI/NSColor", key: "ui/nsColor", keyPath: \.uiColor)
105 |
106 | editValueView(title: "CGColor", key: "cgColor", keyPath: \.cgColor)
107 |
108 | editValueView(title: "CIColor", key: "ciColor", keyPath: \.ciColor)
109 | } header: {
110 | Text("Color")
111 | }
112 | }
113 |
114 | var imageType: some View {
115 | Section {
116 | editValueView(title: "Image", key: "image", keyPath: \.image)
117 |
118 | editValueView(title: "NS/UIImage", key: "ns/uiImage", keyPath: \.nsuiImage)
119 |
120 | editValueView(title: "CGImage", key: "cgImage", keyPath: \.cgImage)
121 |
122 | editValueView(title: "CIImage", key: "ciImage", keyPath: \.ciImage)
123 | } header: {
124 | Text("Image")
125 | }
126 | }
127 |
128 | var notSupportedType: some View {
129 | Section {
130 | NavigationLink("Not Supported") {
131 | EditValueView(key: "item",
132 | binding: $target,
133 | presentationStyle: .push)
134 | }
135 | } header: {
136 | Text("Not Supported")
137 | }
138 | }
139 |
140 | func editValueView(
141 | title: String,
142 | key: String,
143 | keyPath: WritableKeyPath-
144 | ) -> NavigationLink> {
145 | NavigationLink(title) {
146 | EditValueView(
147 | target,
148 | key: key,
149 | keyPath: keyPath,
150 | presentationStyle: .push
151 | )
152 | .onUpdate { newValue in
153 | target[keyPath: keyPath] = newValue
154 | }
155 | }
156 | }
157 | }
158 |
159 | struct ContentView_Previews: PreviewProvider {
160 | static var previews: some View {
161 | ContentView()
162 | }
163 | }
164 |
165 | enum Enum: CaseIterable {
166 | case red, yellow, blue
167 | }
168 | enum Enum2: Int, CaseIterable, DefaultRepresentable {
169 | case red, yellow, blue
170 |
171 | static var defaultValue: Self {
172 | .red
173 | }
174 | }
175 | struct Item {
176 | var name: String
177 | var bool: Bool
178 | var date: Date?
179 | var `enum`: Enum
180 | var enum2: Enum2?
181 | var color: Color
182 | var `codable`: ACodable = .init(text: "", number: 5)
183 | var array = ["AA", "BB"]
184 | var dictionary: [String: Int] = [
185 | "AA": 0,
186 | "BB": 234
187 | ]
188 | var cgColor = UIColor.yellow.cgColor
189 | var uiColor = UIColor.blue
190 | var ciColor = CIColor(color: UIColor.brown)
191 |
192 | var image: Image = .init(systemName: "swift")
193 | var nsuiImage = UIImage(systemName: "swift")
194 | var cgImage = UIImage(systemName: "swift")?.cgImage
195 | var ciImage = UIImage(systemName: "swift")?.ciImage
196 | var anyDictionary: [String: Any] = ["AA": 0]
197 | }
198 |
199 | struct ACodable: Codable {
200 | var text: String
201 | var number: Int
202 | var double: Double = 0.4
203 | var optionalString: String? = "test"
204 | var nested: BCodable = .init(text: "", number: 0)
205 | }
206 |
207 | extension ACodable: DefaultRepresentable {
208 | static var defaultValue: ACodable = .init(text: "", number: 0)
209 | }
210 |
211 | struct BCodable: Codable {
212 | var text: String
213 | var number: Int
214 | var optionalString: String?
215 | }
216 |
--------------------------------------------------------------------------------
/Example/Example/Example.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/Example/ExampleApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleApp.swift
3 | // Example
4 | //
5 | // Created by p-x9 on 2023/07/25.
6 | //
7 | //
8 |
9 | import SwiftUI
10 |
11 | @main
12 | struct ExampleApp: App {
13 | var body: some Scene {
14 | WindowGroup {
15 | ContentView()
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Example/Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Example/Example/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/ExampleTests/ExampleTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleTests.swift
3 | // Example
4 | //
5 | // Created by p-x9 on 2023/07/25.
6 | //
7 | //
8 |
9 | import XCTest
10 |
11 | final class ExampleTests: XCTestCase {
12 |
13 | override func setUpWithError() throws {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 | }
16 |
17 | override func tearDownWithError() throws {
18 | // Put teardown code here. This method is called after the invocation of each test method in the class.
19 | }
20 |
21 | func testExample() throws {
22 | // This is an example of a functional test case.
23 | // Use XCTAssert and related functions to verify your tests produce the correct results.
24 | // Any test you write for XCTest can be annotated as throws and async.
25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
26 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
27 | }
28 |
29 | func testPerformanceExample() throws {
30 | // This is an example of a performance test case.
31 | measure {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/Example/ExampleUITests/ExampleUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleUITests.swift
3 | // Example
4 | //
5 | // Created by p-x9 on 2023/07/25.
6 | //
7 | //
8 |
9 | import XCTest
10 |
11 | final class ExampleUITests: XCTestCase {
12 |
13 | override func setUpWithError() throws {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 |
16 | // In UI tests it is usually best to stop immediately when a failure occurs.
17 | continueAfterFailure = false
18 |
19 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
20 | }
21 |
22 | override func tearDownWithError() throws {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | }
25 |
26 | func testExample() throws {
27 | // UI tests must launch the application that they test.
28 | let app = XCUIApplication()
29 | app.launch()
30 |
31 | // Use XCTAssert and related functions to verify your tests produce the correct results.
32 | }
33 |
34 | func testLaunchPerformance() throws {
35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
36 | // This measures how long it takes to launch your application.
37 | measure(metrics: [XCTApplicationLaunchMetric()]) {
38 | XCUIApplication().launch()
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Example/ExampleUITests/ExampleUITestsLaunchTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleUITestsLaunchTests.swift
3 | // Example
4 | //
5 | // Created by p-x9 on 2023/07/25.
6 | //
7 | //
8 |
9 | import XCTest
10 |
11 | final class ExampleUITestsLaunchTests: XCTestCase {
12 |
13 | override class var runsForEachTargetApplicationUIConfiguration: Bool {
14 | true
15 | }
16 |
17 | override func setUpWithError() throws {
18 | continueAfterFailure = false
19 | }
20 |
21 | func testLaunch() throws {
22 | let app = XCUIApplication()
23 | app.launch()
24 |
25 | // Insert steps here to perform after app launch but before taking a screenshot,
26 | // such as logging into a test account or navigating somewhere in the app
27 |
28 | let attachment = XCTAttachment(screenshot: app.screenshot())
29 | attachment.name = "Launch Screen"
30 | attachment.lifetime = .keepAlways
31 | add(attachment)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Example/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.7
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "",
7 | platforms: [],
8 | products: [],
9 | dependencies: [],
10 | targets: []
11 | )
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 p-x9
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "swift-magic-mirror",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/p-x9/swift-magic-mirror.git",
7 | "state" : {
8 | "revision" : "390e248dd6727e17aeb3949c12bb83e6eac876d1",
9 | "version" : "0.2.0"
10 | }
11 | },
12 | {
13 | "identity" : "swiftui-reflection-view",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/p-x9/swiftui-reflection-view.git",
16 | "state" : {
17 | "revision" : "ce3b810ecfa1b2e308cbb5192ee4c8c43b1eefa8",
18 | "version" : "0.8.0"
19 | }
20 | },
21 | {
22 | "identity" : "swiftuicolor",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/p-x9/SwiftUIColor.git",
25 | "state" : {
26 | "revision" : "aa42f452698cc0f78dcba2c6544f4abf7724602f",
27 | "version" : "0.4.0"
28 | }
29 | }
30 | ],
31 | "version" : 2
32 | }
33 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.7
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "EditValueView",
7 | platforms: [
8 | .iOS(.v14),
9 | .macOS(.v11)
10 | ],
11 | products: [
12 | .library(
13 | name: "EditValueView",
14 | targets: ["EditValueView"]
15 | )
16 | ],
17 | dependencies: [
18 | .package(url: "https://github.com/p-x9/SwiftUIColor.git", .upToNextMajor(from: "0.3.0")),
19 | .package(url: "https://github.com/p-x9/swift-magic-mirror.git", from: "0.2.0"),
20 | .package(url: "https://github.com/p-x9/swiftui-reflection-view.git", from: "0.8.0")
21 | ],
22 | targets: [
23 | .target(
24 | name: "EditValueView",
25 | dependencies: [
26 | .product(name: "SwiftUIColor", package: "SwiftUIColor"),
27 | .product(name: "MagicMirror", package: "swift-magic-mirror"),
28 | .product(name: "ReflectionView", package: "swiftui-reflection-view")
29 | ]
30 | ),
31 | .testTarget(
32 | name: "EditValueViewTests",
33 | dependencies: ["EditValueView"]
34 | )
35 | ]
36 | )
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EditValueView
2 |
3 | Library that makes easy to display property edit screens for SwiftUI.
4 |
5 | ## Demo
6 |
7 | | String | Bool | Int |
8 | | ---- | ---- | ---- |
9 | |  |  |  |
10 |
11 | | Double | Date | Color |
12 | | ---- | ---- | ---- |
13 | |  |  |  |
14 |
15 | | Image | UI/NSImage |
16 | | ---- | ---- |
17 | |  |  |
18 |
19 | | Array | Dictionary |
20 | | ---- | ---- |
21 | |  |  |
22 |
23 | | Enum(CaseIterable) | Enum(CaseIterable & RawRepresentable) |
24 | | ---- | ---- |
25 | |  |  |
26 |
27 | | Codable |
28 | | ---- |
29 | |  |
30 |
31 | ## Supported types
32 | - String
33 | - Bool
34 | - any Numerics
35 | - Date
36 | - Color/UIColor/NSColor/CGColor/CIColor
37 | - Image/UIImage/CGImage/CIImage (iOS Only)
38 | - Array(Codable)
39 | - Dictionary(Codable)
40 | - CaseIterable
41 | - CaseIterable & RawRepresentable
42 | - Codable
43 |
44 | ## Usage
45 | > **Note**
46 | > If you want to use the camera for editing images, you must add a key named `NSCameraUsageDescription` to the info.plist file.
47 |
48 | ### SwiftUI
49 | #### Initialize
50 | - Initialize with key and initial value
51 | ```swift
52 | var name = ""
53 | EditValueView(key: "name", value: name)
54 | .onUpdate { newValue in
55 | name = newValue
56 | }
57 | ```
58 | - Initialize with keyPath
59 | ```swift
60 | EditValueView(target, key: "name", keyPath: \Item.name)
61 | .onUpdate { newValue in
62 | target[keyPath: \.name] = newValue
63 | }
64 | ```
65 | - Initialize with binding
66 | ```swift
67 | @State var name: String = ""
68 | EditValueView(key: "name", binding: $name)
69 | ```
70 |
71 | #### Update Handler
72 | You can receive an edit callback when you press the `save` button.
73 | ```swift
74 | EditValueView(target, key: "name", keyPath: \Item.name)
75 | .onUpdate { newValue in
76 | // update
77 | }
78 | ```
79 |
80 | #### Input Validation
81 | You can validate input values.
82 | ```swift
83 | EditValueView(target, key: "name", keyPath: \Item.name)
84 | .validate { newValue -> Bool in
85 | // input validation
86 | return !name.isEmpty
87 | }
88 | ```
89 |
90 | ### UIKit
91 | ```swift
92 | let vc = EditValueViewController(target, key: "name", keyPath: \Item.name)
93 | vc.onUpdate = { target, newValue in
94 | // update
95 | }
96 | vc.validate = { target, newValue -> Bool in
97 | // input validation
98 | }
99 | ```
100 |
101 | ### Protocol
102 | When using optional types, type hints for Codable cannot be displayed when nil is used.
103 | To avoid such problems, provide a default value in accordance with the protocol named `DefaultRepresentable`.
104 |
105 | ```swift
106 | struct Item: Codable {
107 | var name: String
108 | var date: Date
109 | }
110 |
111 | struct Message: Codable {
112 | var content: String
113 | var item: Item?
114 | }
115 | ```
116 | ```swift
117 | // Confirm to `DefaultRepresentable` protocol
118 | extension Item: DefaultRepresentable {
119 | static var defaultValue: Self {
120 | .init(name: "name", date: Date())
121 | }
122 | }
123 | ```
124 | ```swift
125 | // give default value
126 | EditValueView(target, key: "item", keyPath: \Message.item, defaultValue: .init(name: "name", date: Date()))
127 | ```
128 |
129 | ## License
130 | EditValueView is released under the MIT License. See [LICENSE](./LICENSE)
131 |
--------------------------------------------------------------------------------
/Sources/EditValueView/EditValueView+EditorType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EditValueView+EditorType.swift
3 | //
4 | //
5 | // Created by p-x9 on 2023/07/23.
6 | //
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | extension EditValueView {
13 | enum EditorType {
14 | enum Line {
15 | case single, multi
16 | }
17 | case string
18 | case toggle
19 | case codable(Line)
20 | case caseiterable
21 | case color
22 | case image
23 | case date
24 | case none
25 |
26 | var shouldShowOptionalEditor: Bool {
27 | switch self {
28 | case .image, .codable(.multi), .caseiterable:
29 | return false
30 | default:
31 | return true
32 | }
33 | }
34 | }
35 |
36 | var editorType: EditorType {
37 | switch Value.self {
38 | case is String.Type,
39 | is String?.Type:
40 | return .string
41 |
42 | case is Bool.Type,
43 | is Bool?.Type:
44 | return .toggle
45 |
46 | case is any Numeric.Type,
47 | is any OptionalNumeric.Type:
48 | return .codable(.single)
49 |
50 | case is Date.Type,
51 | is Date?.Type:
52 | return .date
53 |
54 | case is Color.Type,
55 | is Color?.Type:
56 | return .color
57 |
58 | case is CGColor.Type,
59 | is CGColor?.Type:
60 | return .color
61 |
62 | case is NSUIColor.Type,
63 | is NSUIColor?.Type:
64 | return .color
65 |
66 | case is CIColor.Type,
67 | is CIColor?.Type:
68 | return .color
69 |
70 | #if canImport(UIKit)
71 | case is Image.Type,
72 | is Image?.Type:
73 | return .image
74 |
75 | case is NSUIImage.Type,
76 | is NSUIImage?.Type:
77 | return .image
78 |
79 | case is CGImage.Type,
80 | is CGImage?.Type:
81 | return .image
82 |
83 | case is CIImage.Type,
84 | is CIImage?.Type:
85 | return .image
86 | #endif
87 |
88 | case is any CaseIterable.Type,
89 | is any OptionalCaseIterable.Type:
90 | return .caseiterable
91 |
92 | case is any Codable.Type:
93 | return .codable(.multi)
94 |
95 | default:
96 | return .none
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Sources/EditValueView/EditValueView+setValue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EditValueView+setValue.swift
3 | //
4 | //
5 | // Created by p-x9 on 2023/07/23.
6 | //
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | extension EditValueView {
13 | func setDefault() {
14 | if let type = Value.self as? any DefaultRepresentable.Type,
15 | let defaultValue = type.defaultValue as? Value {
16 | value = defaultValue
17 | }
18 | }
19 |
20 | func setNil() {
21 | guard isOptional else { return }
22 |
23 | switch $value {
24 | case let v as Binding:
25 | v.wrappedValue = nil
26 |
27 | case let v as Binding:
28 | v.wrappedValue = nil
29 |
30 | case let v as Binding:
31 | v.wrappedValue = nil
32 |
33 | case let v as Binding:
34 | v.wrappedValue = nil
35 |
36 | case let v as Binding:
37 | v.wrappedValue = nil
38 |
39 | case let v as Binding:
40 | v.wrappedValue = nil
41 |
42 | case let v as Binding:
43 | v.wrappedValue = nil
44 |
45 | #if canImport(UIKit)
46 | case let v as Binding:
47 | v.wrappedValue = nil
48 |
49 | case let v as Binding:
50 | v.wrappedValue = nil
51 |
52 | case let v as Binding:
53 | v.wrappedValue = nil
54 |
55 | case let v as Binding:
56 | v.wrappedValue = nil
57 | #endif
58 |
59 | case _ where Value.self is any Codable.Type,
60 | _ where Value.self is any OptionalNumeric.Type:
61 | if let type = Value.self as? any Codable.Type,
62 | let nilValue = type.value(from: "null") as? Value {
63 | value = nilValue
64 | }
65 |
66 | default:
67 | break
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Sources/EditValueView/EditValueView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EditValueView.swift
3 | //
4 | //
5 | // Created by p-x9 on 2022/10/16.
6 | //
7 | //
8 | import SwiftUI
9 | import SwiftUIColor
10 |
11 | @available(iOS 14, *)
12 | public struct EditValueView: View {
13 | public enum PresentationStyle {
14 | case modal
15 | case push
16 | }
17 |
18 | /// Name of the property to be edited
19 | /// Used for navigation titles and type descriptions.
20 | let key: String
21 |
22 | /// Presentation style.
23 | /// If set to `modal`, it will be wrapped in NavigationView
24 | /// On the other hand, if `push` is specified, it is not wrapped in NavigationView and must contain NavigationView as its parent.
25 | var presentationStyle: PresentationStyle
26 |
27 | /// This is called when editing is completed by pressing the save button.
28 | /// They will be received by a modifier named `onUpdate`.
29 | private var _onUpdate: ((Value) -> Void)?
30 |
31 | /// Used to perform validation checks when editing values
32 | /// They will be received by a modifier named `validate`.
33 | private var _validate: ((Value) -> Bool)?
34 |
35 | /// Value to be edited
36 | @State var value: Value
37 |
38 | /// True if nil set is requested by optionalEditor.
39 | /// Note: Use the `isNil` property to check if a value is actually nil.
40 | @State private var shouldSetNil = false
41 |
42 | /// A boolean value indicating whether the current input value can be converted to the actual type.
43 | @State private var isValidType = true
44 |
45 | /// binding received from the initializer.
46 | /// When the save button is pressed, the binding is updated to indicate the value change.
47 | private var binding: Binding?
48 |
49 | /// A boolean value that indicates whether the current input value can be converted to the actual type and conforms to the validation checks
50 | var isValid: Bool {
51 | isValidType && (_validate?(value) ?? true)
52 | }
53 |
54 | /// A boolean value that indicates whether the type being edited is an Optional type or not
55 | var isOptional: Bool {
56 | Value.self is any OptionalType.Type
57 | }
58 |
59 | /// A boolean value that indicates whether the value is nil
60 | var isNil: Bool {
61 | if let optional = value as? any OptionalType {
62 | return optional.wrapped == nil
63 | }
64 | return false
65 | }
66 |
67 | /// A boolean value that indicates whether optionalEditor should be displayed
68 | var shouldShowOptionalEditor: Bool {
69 | isOptional &&
70 | editorType.shouldShowOptionalEditor
71 | }
72 |
73 | @Environment(\.presentationMode) private var presentationMode
74 |
75 | public var body: some View {
76 | if presentationStyle == .push {
77 | _body
78 | } else {
79 | NavigationView {
80 | _body
81 | }
82 | }
83 | }
84 |
85 | @MainActor
86 | var _body: some View {
87 | GeometryReader{ proxy in
88 | ScrollView {
89 | VStack(alignment: .leading, spacing: 8) {
90 | header
91 |
92 | typeSection
93 | .padding(.top)
94 |
95 | if isOptional && shouldShowOptionalEditor {
96 | optionalEditor
97 | .padding()
98 | .border(Color.iOS(.label), width: 0.5)
99 | .padding(.vertical)
100 | }
101 |
102 | if !shouldSetNil && !isNil || !shouldShowOptionalEditor {
103 | editor
104 | .padding(.vertical)
105 | .layoutPriority(.infinity)
106 | }
107 |
108 | Spacer()
109 | }
110 | .padding()
111 | .frame(minHeight: proxy.size.height)
112 | .navigationTitle(key)
113 | .toolbar {
114 | ToolbarItem(placement: .destructiveAction) {
115 | Button("Save") {
116 | save()
117 | presentationMode.wrappedValue.dismiss()
118 | }
119 | .disabled(!isValid)
120 | }
121 | ToolbarItem(placement: .cancellationAction) {
122 | if presentationStyle != .push {
123 | Button("Cancel") {
124 | presentationMode.wrappedValue.dismiss()
125 | }
126 | }
127 | }
128 | }
129 | }
130 | }
131 | }
132 |
133 | /// Displays key and type information
134 | @ViewBuilder
135 | var header: some View {
136 | VStack(alignment: .leading, spacing: 8) {
137 | HStack {
138 | let string: String = "Key: \(key)"
139 | Text(string)
140 | .font(.system(size: 14, weight: .bold, design: .monospaced))
141 | .foregroundColor(.gray)
142 | Spacer()
143 | }
144 | HStack {
145 | let string: String = "Type: \(Value.self)"
146 | Text(string)
147 | .font(.system(size: 14, weight: .bold, design: .monospaced))
148 | .foregroundColor(.gray)
149 | Spacer()
150 | }
151 | }
152 | }
153 |
154 | /// Displays type information
155 | @ViewBuilder
156 | var typeSection: some View {
157 | HStack {
158 | let type = "\(Value.self)"
159 | Text(type)
160 | .font(.system(size: 14, weight: .bold, design: .monospaced))
161 | .foregroundColor(.gray)
162 | Spacer()
163 | }
164 | .padding()
165 | .background(Color.iOS.secondarySystemFill)
166 | .cornerRadius(8)
167 | }
168 |
169 | @ViewBuilder
170 | var optionalEditor: some View {
171 | Toggle(isOn: $shouldSetNil) {
172 | Text("Set value to `nil`")
173 | }
174 | .onChange(of: shouldSetNil) { newValue in
175 | if newValue {
176 | setNil()
177 | } else {
178 | setDefault()
179 | }
180 | }
181 | }
182 |
183 | @ViewBuilder
184 | var notSupportedView: some View {
185 | Text("this type is currently not supported.")
186 | }
187 |
188 | @ViewBuilder
189 | var editor: some View {
190 | switch $value {
191 | case let v as Binding:
192 | TextEditor(text: v)
193 | .frame(minHeight: 200, maxHeight: .infinity)
194 | .border(Color.iOS(.label), width: 0.5)
195 |
196 | case let v as Binding:
197 | Toggle(key, isOn: v)
198 | .padding()
199 | .border(Color.iOS(.label), width: 0.5)
200 |
201 | case _ where Value.self is any Numeric.Type:
202 | CodableEditorView($value, key: key, isValidType: $isValidType, textStyle: .single)
203 |
204 | case let v as Binding:
205 | DateEditorView(v, key: key)
206 |
207 | case let v as Binding:
208 | ColorEditorView(v, key: key)
209 |
210 | case let v as Binding:
211 | ColorEditorView(v, key: key)
212 |
213 | case let v as Binding:
214 | ColorEditorView(v, key: key)
215 |
216 | case let v as Binding:
217 | ColorEditorView(v, key: key)
218 |
219 | #if canImport(UIKit)
220 | case let v as Binding:
221 | SwiftUIImageEditor(image: .init(v), isOptional: false)
222 |
223 | case let v as Binding:
224 | ImageEditor(v)
225 |
226 | case let v as Binding:
227 | ImageEditor(v)
228 |
229 | case let v as Binding:
230 | ImageEditor(v)
231 | #endif
232 |
233 | case _ where Value.self is any CaseIterable.Type:
234 | CaseIterableEditor($value, key: key)
235 | .border(Color.iOS(.label), width: 0.5)
236 |
237 | /* Optional Type */
238 | case let v as Binding where !isNil:
239 | TextEditor(text: Binding(v)!)
240 | .frame(minHeight: 200, maxHeight: .infinity)
241 | .border(Color.iOS(.label), width: 0.5)
242 |
243 | case let v as Binding where !isNil:
244 | Toggle(key, isOn: Binding(v)!)
245 | .padding()
246 | .border(Color.iOS(.label), width: 0.5)
247 |
248 | case _ where Value.self is any OptionalNumeric.Type:
249 | CodableEditorView($value, key: key, isValidType: $isValidType, textStyle: .single)
250 |
251 | case let v as Binding where !isNil:
252 | DateEditorView(Binding(v)!, key: key)
253 |
254 | case let v as Binding where !isNil:
255 | ColorEditorView(Binding(v)!, key: key)
256 |
257 | case let v as Binding where !isNil:
258 | ColorEditorView(Binding(v)!, key: key)
259 |
260 | case let v as Binding where !isNil:
261 | ColorEditorView(Binding(v)!, key: key)
262 |
263 | case let v as Binding where !isNil:
264 | ColorEditorView(Binding(v)!, key: key)
265 |
266 | #if canImport(UIKit)
267 | case let v as Binding:
268 | SwiftUIImageEditor(image: v, isOptional: true)
269 |
270 | case let v as Binding:
271 | ImageEditor(v)
272 |
273 | case let v as Binding:
274 | ImageEditor(v)
275 |
276 | case let v as Binding:
277 | ImageEditor(v)
278 | #endif
279 |
280 | case _ where Value.self is any OptionalCaseIterable.Type:
281 | CaseIterableEditor($value, key: key)
282 | .border(Color.iOS(.label), width: 0.5)
283 |
284 | /* Other */
285 | case _ where Value.self is any Codable.Type:
286 | CodableEditorView($value, key: key, isValidType: $isValidType)
287 |
288 | default:
289 | if let anyJSONEditor = AnyJSONEditor($value, key: key, isValidType: $isValidType) {
290 | anyJSONEditor
291 | } else {
292 | notSupportedView
293 | }
294 | }
295 | }
296 |
297 | /// Set the process to be performed when the Save button is pressed.
298 | /// - Parameter onUpdate: A closure that receives the edited value
299 | /// - Returns: Self
300 | public func onUpdate(_ onUpdate: ((Value) -> Void)?) -> Self {
301 | var new = self
302 | new._onUpdate = onUpdate
303 | return new
304 | }
305 |
306 | /// Set validation checks.
307 | /// Returning false disables the save button
308 | /// - Parameter validate: A closure that takes the current value after editing and returns the validation result
309 | /// - Returns: Self
310 | public func validate(_ validate: ((Value) -> Bool)?) -> Self {
311 | var new = self
312 | new._validate = validate
313 | return new
314 | }
315 |
316 | private func save() {
317 | binding?.wrappedValue = value
318 | _onUpdate?(value)
319 | }
320 | }
321 |
322 | extension EditValueView {
323 | /// Initialize with key and value
324 | /// - Parameters:
325 | /// - key: Name of the property to be edited. Used for navigation titles and type descriptions.
326 | /// - value: Initial value of the value to be edited
327 | /// - presentationStyle: Presentation style. If set to `modal`,
328 | /// it will be wrapped in NavigationView.
329 | /// On the other hand, if `push` is specified, it is not wrapped in NavigationView and must contain NavigationView as its parent.
330 | public init(key: String, value: Value, presentationStyle: PresentationStyle = .modal) {
331 | self.key = key
332 | self._value = .init(initialValue: value)
333 | self.presentationStyle = presentationStyle
334 |
335 | self._shouldSetNil = .init(initialValue: isNil)
336 | }
337 |
338 | /// Initialize with keyPath
339 | /// - Parameters:
340 | /// - target: Target object that has the property to be edited.
341 | /// - key: Name of the property to be edited. Used for navigation titles and type descriptions.
342 | /// - keyPath: keyPath of the property to be edited.
343 | /// - presentationStyle: Presentation style. If set to `modal`,
344 | /// it will be wrapped in NavigationView.
345 | /// On the other hand, if `push` is specified, it is not wrapped in NavigationView and must contain NavigationView as its parent.
346 | public init(_ target: Root, key: String, keyPath: WritableKeyPath, presentationStyle: PresentationStyle = .modal) {
347 | self.init(key: key,
348 | value: target[keyPath: keyPath],
349 | presentationStyle: presentationStyle)
350 | }
351 |
352 | /// Initialize with binding
353 | /// - Parameters:
354 | /// - key: Name of the property to be edited. Used for navigation titles and type descriptions.
355 | /// - binding: Binder for the value to be edited
356 | /// - presentationStyle: Presentation style. If set to `modal`,
357 | /// it will be wrapped in NavigationView.
358 | /// On the other hand, if `push` is specified, it is not wrapped in NavigationView and must contain NavigationView as its parent.
359 | public init(key: String, binding: Binding, presentationStyle: PresentationStyle = .modal) {
360 | self.key = key
361 | self._value = .init(initialValue: binding.wrappedValue)
362 | self.binding = binding
363 | self.presentationStyle = presentationStyle
364 |
365 | self._shouldSetNil = .init(initialValue: isNil)
366 | }
367 | }
368 |
369 | extension EditValueView {
370 | public func presentationStyle(_ style: PresentationStyle) -> Self {
371 | set(style, for: \.presentationStyle)
372 | }
373 | }
374 |
375 | #if DEBUG
376 | enum Enum: CaseIterable {
377 | case red, yellow, blue
378 | }
379 | enum Enum2: Int, CaseIterable, DefaultRepresentable {
380 | case red, yellow, blue
381 |
382 | static var defaultValue: Self {
383 | .red
384 | }
385 | }
386 | struct Item {
387 | var name: String
388 | var bool: Bool
389 | var date: Date
390 | var `enum`: Enum
391 | var enum2: Enum2?
392 | var image: Image = .init(systemName: "swift")
393 | var color: Color
394 | var `codable`: ACodable = .init(text: "", number: 5)
395 | var array = ["AA", "BB"]
396 | var dictionary: [String: Int] = [
397 | "AA": 0,
398 | "BB": 234
399 | ]
400 | var anyDictionary: [String: Any] = ["AA": 0]
401 |
402 | var cgColor = NSUIColor.yellow.cgColor
403 | var uiColor = NSUIColor.blue
404 | var ciColor = CIColor(color: NSUIColor.brown)
405 |
406 | #if canImport(UIKit)
407 | var nsuiImage = NSUIImage(systemName: "swift")
408 | var cgImage = NSUIImage(systemName: "swift")?.cgImage
409 | var ciImage = NSUIImage(systemName: "swift")?.ciImage
410 | #endif
411 | }
412 |
413 | struct ACodable: Codable {
414 | var text: String
415 | var number: Int
416 | var double: Double = 0.4
417 | var optionalString: String? = "test"
418 | var nested: BCodable = .init(text: "", number: 0)
419 | }
420 |
421 | extension ACodable: DefaultRepresentable {
422 | static var defaultValue: ACodable = .init(text: "", number: 0)
423 | }
424 |
425 | struct BCodable: Codable {
426 | var text: String
427 | var number: Int
428 | var optionalString: String?
429 | }
430 |
431 | let target: Item = .init(name: "Hello!!",
432 | bool: true,
433 | date: Date(),
434 | enum: .red,
435 | enum2: .blue,
436 | color: .white)
437 |
438 | struct EditValueView_Preview: PreviewProvider {
439 | static var previews: some View {
440 | Group {
441 | Group {
442 | EditValueView(target, key: "name", keyPath: \Item.name)
443 | .validate { value in
444 | value != "Test"
445 | }
446 | .previewDisplayName("String")
447 |
448 | EditValueView(target, key: "bool", keyPath: \Item.bool)
449 | .previewDisplayName("Bool")
450 |
451 | EditValueView(target, key: "date", keyPath: \Item.date)
452 | .previewDisplayName("Date")
453 |
454 | EditValueView(target, key: "number", keyPath: \Item.codable.number)
455 | .previewDisplayName("Int")
456 |
457 | EditValueView(target, key: "double", keyPath: \Item.codable.double)
458 | .previewDisplayName("Double")
459 | }
460 |
461 | Group {
462 | EditValueView(target, key: "enum", keyPath: \Item.enum)
463 | .previewDisplayName("Enum(CaseIterable)")
464 |
465 | EditValueView(target, key: "enum", keyPath: \Item.enum2)
466 | .previewDisplayName("Enum(CaseIterable & RawRepresentable)")
467 |
468 | EditValueView(target, key: "array", keyPath: \Item.array)
469 | .previewDisplayName("Array")
470 |
471 | EditValueView(target, key: "dictionary", keyPath: \Item.dictionary)
472 | .previewDisplayName("Dictionary")
473 | }
474 |
475 | EditValueView(target, key: "codable", keyPath: \Item.codable)
476 | .previewDisplayName("Codable")
477 |
478 | EditValueView(target, key: "anyDictionary", keyPath: \Item.anyDictionary)
479 | .previewDisplayName("Any Dictionary")
480 | }
481 | }
482 | }
483 |
484 | struct EditValueView_Color_Preview: PreviewProvider {
485 | static var previews: some View {
486 | Group {
487 | EditValueView(target, key: "color", keyPath: \Item.color)
488 | .previewDisplayName("Color")
489 |
490 | EditValueView(target, key: "ui/nsColor", keyPath: \Item.uiColor)
491 | .previewDisplayName("UI/NSColor")
492 |
493 | EditValueView(target, key: "cgColor", keyPath: \Item.cgColor)
494 | .previewDisplayName("CGColor")
495 |
496 | EditValueView(target, key: "ciColor", keyPath: \Item.ciColor)
497 | .previewDisplayName("CIColor")
498 | }
499 | }
500 | }
501 |
502 | #if canImport(UIKit)
503 | struct EditValueView_Image_Preview: PreviewProvider {
504 | static var previews: some View {
505 | Group {
506 | EditValueView(target, key: "image", keyPath: \Item.image)
507 | .previewDisplayName("Image")
508 |
509 | EditValueView(target, key: "ns/uiImage", keyPath: \Item.nsuiImage)
510 | .previewDisplayName("NS/UIImage")
511 |
512 | EditValueView(target, key: "cgImage", keyPath: \Item.cgImage)
513 | .previewDisplayName("CGImage")
514 |
515 | EditValueView(target, key: "ciImage", keyPath: \Item.ciImage)
516 | .previewDisplayName("CIImage")
517 | }
518 | }
519 | }
520 | #endif
521 | #endif
522 |
--------------------------------------------------------------------------------
/Sources/EditValueView/Editors/AnyJSONEditor.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import SwiftUIColor
3 |
4 | struct AnyJSONEditor: View {
5 | let key: String
6 |
7 | @Binding private var value: Value
8 | @Binding private var isValidType: Bool
9 |
10 | @State private var text: String
11 |
12 | init?(_ value: Binding, key: String, isValidType: Binding) {
13 | self._value = value
14 | self._isValidType = isValidType
15 | self.key = key
16 |
17 | guard JSONSerialization.isValidJSONObject(value.wrappedValue),
18 | let json = try? JSONSerialization.data(withJSONObject: value.wrappedValue, options: [.prettyPrinted, .sortedKeys]),
19 | let jsonString = String(data: json, encoding: .utf8)
20 | else {
21 | return nil
22 | }
23 |
24 | self._text = .init(initialValue: jsonString)
25 | }
26 |
27 | var body: some View {
28 | VStack {
29 | editor
30 | .onChange(of: text) { newValue in
31 | textChanged(text: newValue)
32 | }
33 | }
34 | }
35 |
36 | @ViewBuilder
37 | var editor: some View {
38 | TextEditor(text: $text)
39 | .frame(minHeight: 200, maxHeight: .infinity)
40 | .border(Color.iOS(.label), width: 0.5)
41 | .padding(.vertical)
42 | }
43 |
44 | func textChanged(text: String) {
45 | guard let data = text.data(using: .utf8),
46 | let value = try? JSONSerialization.jsonObject(with: data) as? Value
47 | else {
48 | isValidType = false
49 | return
50 | }
51 | self.value = value
52 | isValidType = true
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/EditValueView/Editors/CaseIterableEditor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CaseIterableEditor.swift
3 | //
4 | //
5 | // Created by p-x9 on 2022/10/20.
6 | //
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct CaseIterableEditor: View {
12 |
13 | let key: String
14 | @Binding private var value: Value
15 | @State private var index: Int = 0
16 |
17 | private let allCases: [Value]
18 |
19 | var isNil: Bool {
20 | if let optional = value as? any OptionalType {
21 | return optional.wrapped == nil
22 | }
23 | return false
24 | }
25 |
26 | init(_ value: Binding, key: String) {
27 | self._value = value
28 | self.key = key
29 |
30 | switch Value.self {
31 | case let type as any CaseIterable.Type:
32 | self.allCases = type.allCases as! [Value]
33 | case let type as any OptionalCaseIterable.Type:
34 | self.allCases = type.optionalAllCases as! [Value]
35 | default:
36 | fatalError()
37 | }
38 |
39 | self._index = .init(wrappedValue: allCases.firstIndex(where: { "\($0)" == "\(value.wrappedValue)" }) ?? 0)
40 | }
41 |
42 | var body: some View {
43 | VStack {
44 | if allCases.isEmpty {
45 | Text("this type is currently not supported.")
46 | } else {
47 | editor
48 | }
49 | }
50 | .onChange(of: index) { newValue in
51 | value = allCases[newValue]
52 | }
53 | }
54 |
55 | @ViewBuilder
56 | var editor: some View {
57 | switch Value.self {
58 | case let type as any (CaseIterable & RawRepresentable).Type:
59 | let allCases = type.allCases as! [Value]
60 | Picker(key, selection: $index) {
61 | ForEach(0..: View {
14 |
15 | enum TextStyle {
16 | case single
17 | case multiline
18 | }
19 |
20 | let key: String
21 |
22 | @Binding private var value: Value
23 | @Binding private var isValidType: Bool
24 |
25 | @State private var text: String
26 | @State private var textStyle: TextStyle
27 |
28 | init(_ value: Binding, key: String, isValidType: Binding, textStyle: TextStyle = .multiline) {
29 | self._value = value
30 | self._isValidType = isValidType
31 | self.key = key
32 |
33 | self._textStyle = .init(initialValue: textStyle)
34 |
35 | let codableValue = value.wrappedValue as! Codable
36 | self._text = .init(initialValue: codableValue.jsonString ?? "")
37 | }
38 |
39 | var body: some View {
40 | VStack {
41 | if textStyle == .multiline {
42 | typeDescriptionView
43 | .layoutPriority(1)
44 | }
45 |
46 | editor
47 | .onChange(of: text) { newValue in
48 | textChanged(text: newValue)
49 | }
50 | }
51 | }
52 |
53 | @ViewBuilder
54 | var typeDescriptionView: some View {
55 | HStack(alignment: .center, spacing: 0) {
56 | TypeInfoView(valueForTypeDescription)
57 | .frame(maxWidth: .infinity, alignment: .topLeading)
58 | Spacer()
59 | }
60 | .font(.system(size: 14, design: .monospaced))
61 | .background(Color.iOS.secondarySystemFill)
62 | .cornerRadius(8)
63 | }
64 |
65 | @ViewBuilder
66 | var editor: some View {
67 | switch textStyle {
68 | case .single:
69 | TextField("", text: $text)
70 | .padding()
71 | .border(Color.iOS(.label), width: 0.5)
72 |
73 | case .multiline:
74 | TextEditor(text: $text)
75 | .frame(minHeight: 200, maxHeight: .infinity)
76 | .border(Color.iOS(.label), width: 0.5)
77 | .padding(.vertical)
78 | }
79 | }
80 |
81 | func textChanged(text: String) {
82 | let type = Value.self as! Codable.Type
83 | guard let value = type.value(from: text) as? Value else {
84 | isValidType = false
85 | return
86 | }
87 | self.value = value
88 | isValidType = true
89 | }
90 | }
91 |
92 | extension CodableEditorView {
93 | var valueForTypeDescription: Any {
94 | if let type = Value.self as? any DefaultRepresentable.Type,
95 | let value = type.defaultValue as? Value {
96 | return value
97 | }
98 | return value
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Sources/EditValueView/Editors/ColorEditorView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorEditorView.swift
3 | //
4 | //
5 | // Created by p-x9 on 2022/10/30.
6 | //
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct ColorEditorView: View {
12 |
13 | let key: String
14 |
15 | @Binding private var value: Value
16 |
17 | @State private var color: CGColor
18 |
19 | init(_ value: Binding, key: String) {
20 | self._value = value
21 | self.key = key
22 |
23 | _color = .init(initialValue: Self.toCGColor(value.wrappedValue) ?? .defaultValue)
24 | }
25 |
26 | var body: some View {
27 | editor
28 | .onChange(of: color) { newValue in
29 | colorChanged(newValue)
30 | }
31 | }
32 |
33 | @ViewBuilder
34 | var editor: some View {
35 | ColorPicker(key, selection: $color)
36 | .padding()
37 | .border(Color.iOS(.label), width: 0.5)
38 | }
39 |
40 | func colorChanged(_ cgColor: CGColor) {
41 | switch Value.self {
42 | case is NSUIColor.Type:
43 | let v = NSUIColor(cgColor: cgColor)
44 | value = v as! Value
45 | break
46 |
47 | case is Color.Type:
48 | let v: Color
49 | if #available(iOS 15.0, macOS 12.0, *) {
50 | v = Color(cgColor: cgColor)
51 | } else {
52 | v = Color(cgColor)
53 | }
54 | value = v as! Value
55 |
56 | case is CIColor.Type:
57 | let v = CIColor(cgColor: cgColor)
58 | value = v as! Value
59 |
60 | case is CGColor.Type:
61 | value = cgColor as! Value
62 |
63 | default:
64 | break
65 | }
66 | }
67 |
68 | static func toCGColor(_ value: Value) -> CGColor? {
69 | switch value {
70 | case let v as NSUIColor:
71 | return v.cgColor
72 |
73 | case let v as CIColor:
74 | return NSUIColor(ciColor: v).cgColor
75 |
76 | case let v as Color:
77 | return v.cgColor
78 |
79 | case let v as CGColor where Value.self is CGColor.Type: // Conditional downcast to CoreFoundation type 'CGColor' will always succeed
80 | return v.copy()
81 |
82 | default:
83 | return nil
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Sources/EditValueView/Editors/DateEditorView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateEditorView.swift
3 | //
4 | //
5 | // Created by p-x9 on 2022/10/21.
6 | //
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct DateEditorView: View {
12 |
13 | let key: String
14 | @Binding private var value: Date
15 |
16 | init(_ value: Binding, key: String) {
17 | _value = value
18 | self.key = key
19 | }
20 |
21 | var body: some View {
22 | VStack {
23 | Text(value.description)
24 | .frame(maxWidth: .infinity)
25 | .border(Color.iOS(.label), width: 0.5)
26 | DatePicker(key, selection: $value)
27 | .datePickerStyle(.graphical)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/EditValueView/Editors/Image/ImageEditor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageEditor.swift
3 | //
4 | //
5 | // Created by p-x9 on 2023/07/25.
6 | //
7 | //
8 |
9 | import SwiftUI
10 |
11 | #if canImport(UIKit)
12 | struct ImageEditor: View {
13 | enum SourceType: Int, Identifiable {
14 | case library
15 | case camera
16 |
17 | var id: Int { rawValue }
18 |
19 | var type: UIImagePickerController.SourceType {
20 | switch self {
21 | case .library: return .photoLibrary
22 | case .camera: return .camera
23 | }
24 | }
25 | }
26 |
27 | @Binding var value: Value
28 |
29 | @State var image: NSUIImage?
30 | @State var sourceTypeOfPicker: SourceType?
31 | @State var isPresentedActionSheet = false
32 | @State var isPresentedFileImporter = false
33 |
34 | /// A boolean value that indicates whether the type being edited is an Optional type or not
35 | var isOptional: Bool {
36 | Value.self is any OptionalType.Type
37 | }
38 |
39 | init(_ value: Binding) {
40 | self._value = value
41 |
42 | let nsuiImage = Self.toNSUIImage(value.wrappedValue)
43 | _image = .init(initialValue: nsuiImage)
44 | }
45 |
46 | var body: some View {
47 | VStack {
48 | if image != nil {
49 | infoSection
50 | }
51 |
52 | HStack {
53 | Text("tap to select image")
54 | .foregroundColor(.gray)
55 | .font(.footnote)
56 | .padding(.top)
57 | Spacer()
58 | }
59 |
60 | editor
61 | .onTapGesture {
62 | isPresentedActionSheet = true
63 | }
64 | .onChange(of: image) { newValue in
65 | imageChanged(newValue)
66 | }
67 | .onDrop(of: [.image], isTargeted: nil, perform: handleDropImage(providers:))
68 | }
69 | .sheet(item: $sourceTypeOfPicker) { type in
70 | ImagePicker(sourceType: type.type, selectedImage: $image)
71 | }
72 | .actionSheet(isPresented: $isPresentedActionSheet) {
73 | actionSheet
74 | }
75 | .fileImporter(
76 | isPresented: $isPresentedFileImporter,
77 | allowedContentTypes: [.image],
78 | allowsMultipleSelection: false
79 | ) { result in
80 | if case let .success(urls) = result,
81 | let url = urls.first,
82 | url.startAccessingSecurityScopedResource() {
83 | image = NSUIImage(contentsOfFile: url.path)
84 | }
85 | }
86 | }
87 |
88 | @ViewBuilder
89 | var infoSection: some View {
90 | if let image {
91 | VStack(alignment: .leading, spacing: 8) {
92 | let size = """
93 | width: \(String(format: "%.1f", image.size.width))
94 | height: \(String(format: "%.1f", image.size.height))
95 | """
96 | HStack {
97 | Text(size)
98 | .font(.system(size: 14, weight: .bold, design: .monospaced))
99 | .foregroundColor(.gray)
100 | Spacer()
101 | }
102 | }
103 | .padding()
104 | .background(Color.iOS.secondarySystemFill)
105 | .cornerRadius(8)
106 | } else {
107 | EmptyView()
108 | }
109 | }
110 |
111 | @ViewBuilder
112 | var editor: some View {
113 | if let image {
114 | Image(uiImage: image)
115 | .renderingMode(.original)
116 | .resizable()
117 | .scaledToFit()
118 | .padding()
119 | .border(Color.iOS(.label), width: 0.5)
120 | } else if isOptional {
121 | Color.iOS.secondarySystemFill
122 | .overlay(
123 | Text("current value is nil")
124 | .foregroundColor(.gray)
125 | )
126 | } else {
127 | Text("this type is currently not supported.")
128 | }
129 | }
130 |
131 | var actionSheet: ActionSheet {
132 | var buttons: [ActionSheet.Button] = [
133 | .default(Text("Photo Library")) {
134 | sourceTypeOfPicker = .library
135 | },
136 | .default(Text("Camera")) {
137 | sourceTypeOfPicker = .camera
138 | },
139 | .default(Text("File")) {
140 | isPresentedFileImporter = true
141 | },
142 | .destructive(Text("Set nil")) {
143 | image = nil
144 | },
145 | .cancel()
146 | ]
147 |
148 | if !isOptional {
149 | buttons.remove(at: buttons.count - 2)
150 | }
151 |
152 | return ActionSheet(
153 | title: Text("Select source"),
154 | buttons: buttons
155 | )
156 | }
157 | }
158 |
159 | extension ImageEditor {
160 | @MainActor
161 | func imageChanged(_ nsuiImage: NSUIImage?) {
162 | guard let nsuiImage else {
163 | setNil()
164 | return
165 | }
166 |
167 | switch $value {
168 | case let v as Binding:
169 | v.wrappedValue = nsuiImage
170 |
171 | case let v as Binding:
172 | if let ciImage = nsuiImage.toCIImage() {
173 | v.wrappedValue = ciImage
174 | }
175 |
176 | case let v as Binding where Value.self is CGImage.Type:
177 | if let cgImage = nsuiImage.cgImage {
178 | v.wrappedValue = cgImage
179 | }
180 |
181 | case let v as Binding:
182 | v.wrappedValue = nsuiImage
183 |
184 | case let v as Binding:
185 | v.wrappedValue = nsuiImage.toCIImage()
186 |
187 | case let v as Binding where Value.self is CGImage?.Type:
188 | v.wrappedValue = nsuiImage.cgImage
189 |
190 | default:
191 | break
192 | }
193 | }
194 |
195 | @MainActor
196 | func setNil() {
197 | guard isOptional else { return }
198 |
199 | switch $value {
200 | case let v as Binding:
201 | v.wrappedValue = nil
202 | case let v as Binding:
203 | v.wrappedValue = nil
204 | case let v as Binding:
205 | v.wrappedValue = nil
206 | default:
207 | break
208 | }
209 | }
210 | }
211 |
212 | extension ImageEditor {
213 | static func toNSUIImage(_ value: Value) -> NSUIImage? {
214 | switch value {
215 | case let v as NSUIImage:
216 | return v
217 |
218 | case let v as CIImage:
219 | return v.uiImage
220 |
221 | case let v as CGImage where Value.self is CGImage.Type:
222 | return .init(cgImage: v)
223 |
224 | case let v as CGImage where Value.self is CGImage?.Type:
225 | return .init(cgImage: v)
226 |
227 | default:
228 | return nil
229 | }
230 | }
231 | }
232 |
233 | extension ImageEditor {
234 | func handleDropImage(providers: [NSItemProvider]) -> Bool {
235 | guard let provider = providers.first else { return false }
236 |
237 | provider.loadObject(ofClass: NSUIImage.self) { item, error in
238 | guard let image = item as? NSUIImage else { return }
239 | DispatchQueue.main.async {
240 | self.image = image
241 | }
242 | }
243 |
244 | return true
245 | }
246 | }
247 |
248 | #endif
249 |
--------------------------------------------------------------------------------
/Sources/EditValueView/Editors/Image/Picker/ImagePicker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImagePicker.swift
3 | //
4 | //
5 | // Created by p-x9 on 2023/07/25.
6 | //
7 | //
8 |
9 | #if canImport(UIKit)
10 | import SwiftUI
11 | import UIKit
12 |
13 | struct ImagePicker: UIViewControllerRepresentable {
14 | typealias UIViewControllerType = UIImagePickerController
15 |
16 | var sourceType: UIImagePickerController.SourceType = .photoLibrary
17 |
18 | @Binding var selectedImage: NSUIImage?
19 | @Environment(\.presentationMode) private var presentationMode
20 |
21 | func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIViewControllerType {
22 |
23 | let imagePicker = UIImagePickerController()
24 | imagePicker.allowsEditing = false
25 | imagePicker.sourceType = sourceType
26 | imagePicker.delegate = context.coordinator
27 |
28 | return imagePicker
29 | }
30 |
31 | func updateUIViewController(_ uiViewController: UIViewControllerType, context: UIViewControllerRepresentableContext) {
32 |
33 | }
34 |
35 | func makeCoordinator() -> Coordinator {
36 | return Coordinator(self)
37 | }
38 |
39 | final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
40 |
41 | var parent: ImagePicker
42 |
43 | init(_ parent: ImagePicker) {
44 | self.parent = parent
45 | }
46 |
47 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
48 |
49 | if let image = info[.originalImage] as? UIImage {
50 | parent.selectedImage = image
51 | }
52 |
53 | parent.presentationMode.wrappedValue.dismiss()
54 | }
55 | }
56 | }
57 | #endif
58 |
--------------------------------------------------------------------------------
/Sources/EditValueView/Editors/Image/Picker/SwiftUIImagePicker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftUIImagePicker.swift
3 | //
4 | //
5 | // Created by p-x9 on 2023/07/25.
6 | //
7 | //
8 |
9 | #if canImport(UIKit)
10 |
11 | import SwiftUI
12 | import UIKit
13 |
14 | struct SwiftUIImagePicker: UIViewControllerRepresentable {
15 | typealias UIViewControllerType = UIImagePickerController
16 |
17 | var sourceType: UIImagePickerController.SourceType = .photoLibrary
18 |
19 | @Binding var selectedImage: Image?
20 | @Environment(\.presentationMode) private var presentationMode
21 |
22 | func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIViewControllerType {
23 |
24 | let imagePicker = UIImagePickerController()
25 | imagePicker.allowsEditing = false
26 | imagePicker.sourceType = sourceType
27 | imagePicker.delegate = context.coordinator
28 |
29 | return imagePicker
30 | }
31 |
32 | func updateUIViewController(_ uiViewController: UIViewControllerType, context: UIViewControllerRepresentableContext) {
33 |
34 | }
35 |
36 | func makeCoordinator() -> Coordinator {
37 | return Coordinator(self)
38 | }
39 |
40 | final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
41 |
42 | var parent: SwiftUIImagePicker
43 |
44 | init(_ parent: SwiftUIImagePicker) {
45 | self.parent = parent
46 | }
47 |
48 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
49 |
50 | if let image = info[.originalImage] as? UIImage {
51 | parent.selectedImage = Image(uiImage: image)
52 | }
53 |
54 | parent.presentationMode.wrappedValue.dismiss()
55 | }
56 | }
57 | }
58 | #endif
59 |
--------------------------------------------------------------------------------
/Sources/EditValueView/Editors/Image/SwiftUIImageEditor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftUIImageEditor.swift
3 | //
4 | //
5 | // Created by p-x9 on 2023/07/25.
6 | //
7 | //
8 |
9 | import SwiftUI
10 |
11 | #if canImport(UIKit)
12 | struct SwiftUIImageEditor: View {
13 | enum SourceType: Int, Identifiable {
14 | case library
15 | case camera
16 |
17 | var id: Int { rawValue }
18 |
19 | var type: UIImagePickerController.SourceType {
20 | switch self {
21 | case .library: return .photoLibrary
22 | case .camera: return .camera
23 | }
24 | }
25 | }
26 |
27 | @Binding var image: Image?
28 | @State var sourceTypeOfPicker: SourceType?
29 | @State var isPresentedActionSheet = false
30 | @State var isPresentedFileImporter = false
31 |
32 | /// A boolean value that indicates whether the type being edited is an Optional type or not
33 | var isOptional: Bool
34 |
35 | var body: some View {
36 | VStack {
37 | HStack {
38 | Text("tap to select image")
39 | .foregroundColor(.gray)
40 | .font(.footnote)
41 | Spacer()
42 | }
43 |
44 | editor
45 | .onTapGesture {
46 | isPresentedActionSheet = true
47 | }
48 | .onDrop(of: [.image], isTargeted: nil, perform: handleDropImage(providers:))
49 | }
50 | .sheet(item: $sourceTypeOfPicker) { type in
51 | SwiftUIImagePicker(sourceType: type.type, selectedImage: $image)
52 | }
53 | .actionSheet(isPresented: $isPresentedActionSheet) {
54 | actionSheet
55 | }
56 | .fileImporter(
57 | isPresented: $isPresentedFileImporter,
58 | allowedContentTypes: [.image],
59 | allowsMultipleSelection: false
60 | ) { result in
61 | if case let .success(urls) = result,
62 | let url = urls.first,
63 | url.startAccessingSecurityScopedResource(),
64 | let nsuiImage = NSUIImage(contentsOfFile: url.path) {
65 | image = Image(uiImage: nsuiImage)
66 | }
67 | }
68 | }
69 |
70 | @ViewBuilder
71 | var editor: some View {
72 | if let image {
73 | image
74 | .renderingMode(.original)
75 | .resizable()
76 | .scaledToFit()
77 | .padding()
78 | .border(Color.iOS(.label), width: 0.5)
79 | } else {
80 | Color.iOS.secondarySystemFill
81 | .overlay(
82 | Text("current value is nil")
83 | .foregroundColor(.gray)
84 | )
85 | }
86 | }
87 |
88 | var actionSheet: ActionSheet {
89 | var buttons: [ActionSheet.Button] = [
90 | .default(Text("Photo Library")) {
91 | sourceTypeOfPicker = .library
92 | },
93 | .default(Text("Camera")) {
94 | sourceTypeOfPicker = .camera
95 | },
96 | .default(Text("File")) {
97 | isPresentedFileImporter = true
98 | },
99 | .destructive(Text("Set nil")) {
100 | image = nil
101 | },
102 | .cancel()
103 | ]
104 |
105 | if !isOptional {
106 | buttons.remove(at: buttons.count - 2)
107 | }
108 |
109 | return ActionSheet(
110 | title: Text("Select source"),
111 | buttons: buttons
112 | )
113 | }
114 | }
115 |
116 | extension SwiftUIImageEditor {
117 | func handleDropImage(providers: [NSItemProvider]) -> Bool {
118 | guard let provider = providers.first else { return false }
119 |
120 | provider.loadObject(ofClass: NSUIImage.self) { item, error in
121 | guard let image = item as? NSUIImage else { return }
122 | DispatchQueue.main.async {
123 | self.image = Image(uiImage: image)
124 | }
125 | }
126 |
127 | return true
128 | }
129 | }
130 |
131 | #endif
132 |
--------------------------------------------------------------------------------
/Sources/EditValueView/Extensions/CIImage+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CIImage+.swift
3 | //
4 | //
5 | // Created by p-x9 on 2023/07/28.
6 | //
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | extension CIImage {
13 | var uiImage: UIImage? {
14 | let context = CIContext()
15 | if let cgImage = context.createCGImage(self, from: self.extent) {
16 | return .init(cgImage: cgImage)
17 | }
18 | return UIImage(ciImage: self)
19 | }
20 | }
21 | #endif
22 |
--------------------------------------------------------------------------------
/Sources/EditValueView/Extensions/Codable+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Codable+.swift
3 | //
4 | //
5 | // Created by p-x9 on 2022/10/22.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | extension Encodable {
12 | private static var jsonEncoder: JSONEncoder {
13 | let encoder = JSONEncoder()
14 | encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
15 | return encoder
16 | }
17 |
18 | var jsonString: String? {
19 | guard let data = try? Self.jsonEncoder.encode(self) else {
20 | return nil
21 | }
22 | return String(data: data, encoding: .utf8)
23 | }
24 | }
25 |
26 | extension Decodable {
27 | private static var jsonDecoder: JSONDecoder {
28 | let decoder = JSONDecoder()
29 | return decoder
30 | }
31 |
32 | static func value(from jsonString: String) -> Self? {
33 | guard let data = jsonString.data(using: .utf8) else {
34 | return nil
35 | }
36 | return try? Self.jsonDecoder.decode(Self.self, from: data)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/EditValueView/Extensions/UIImage+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage.swift
3 | //
4 | //
5 | // Created by p-x9 on 2023/07/28.
6 | //
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 |
12 | extension UIImage {
13 | func toCIImage() -> CIImage? {
14 | if let ciImage {
15 | return ciImage
16 | } else if let cgImage {
17 | return CIImage(cgImage: cgImage)
18 | }
19 | return nil
20 | }
21 | }
22 | #endif
23 |
--------------------------------------------------------------------------------
/Sources/EditValueView/Extensions/View+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // View+.swift
3 | //
4 | //
5 | // Created by p-x9 on 2023/07/30.
6 | //
7 | //
8 |
9 | import SwiftUI
10 |
11 | extension View {
12 | func set(_ value: T, for keyPath: WritableKeyPath) -> Self {
13 | var new = self
14 | new[keyPath: keyPath] = value
15 | return new
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/EditValueView/Protocol/DefaultRepresentable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DefaultRepresentable.swift
3 | //
4 | //
5 | // Created by p-x9 on 2022/10/17.
6 | //
7 | //
8 | import Foundation
9 | import CoreGraphics
10 |
11 | public protocol DefaultRepresentable {
12 | static var defaultValue: Self { get }
13 | }
14 |
15 | extension String: DefaultRepresentable {
16 | public static var defaultValue: String {
17 | ""
18 | }
19 | }
20 | extension Int: DefaultRepresentable {
21 | public static var defaultValue: Int {
22 | 0
23 | }
24 | }
25 |
26 | extension Float: DefaultRepresentable {
27 | public static var defaultValue: Float {
28 | 0.0
29 | }
30 | }
31 |
32 | extension Double: DefaultRepresentable {
33 | public static var defaultValue: Double {
34 | 0.0
35 | }
36 | }
37 |
38 | extension NSNumber: DefaultRepresentable {
39 | public static var defaultValue: Self {
40 | 0
41 | }
42 | }
43 |
44 | extension Bool: DefaultRepresentable {
45 | public static var defaultValue: Bool {
46 | false
47 | }
48 | }
49 |
50 | extension Date: DefaultRepresentable {
51 | public static var defaultValue: Date {
52 | .init()
53 | }
54 | }
55 |
56 | extension Data: DefaultRepresentable {
57 | public static var defaultValue: Data {
58 | .init()
59 | }
60 | }
61 |
62 | extension Array: DefaultRepresentable {
63 | public static var defaultValue: [Element] {
64 | []
65 | }
66 | }
67 |
68 | extension Dictionary: DefaultRepresentable {
69 | public static var defaultValue: [Key: Value] {
70 | [:]
71 | }
72 | }
73 |
74 | extension CGFloat: DefaultRepresentable {
75 | public static var defaultValue: CGFloat {
76 | 0.0
77 | }
78 | }
79 |
80 | extension CGSize: DefaultRepresentable {
81 | public static var defaultValue: CGSize {
82 | .init(width: 0, height: 0)
83 | }
84 | }
85 |
86 | extension CGPoint: DefaultRepresentable {
87 | public static var defaultValue: CGPoint {
88 | .init(x: 0, y: 0)
89 | }
90 | }
91 |
92 | extension CGRect: DefaultRepresentable {
93 | public static var defaultValue: CGRect {
94 | .init(origin: .defaultValue, size: .defaultValue)
95 | }
96 | }
97 |
98 | extension CGColor: DefaultRepresentable {
99 | public static var defaultValue: Self {
100 | .init(red: 0, green: 0, blue: 0, alpha: 1)
101 | }
102 | }
103 |
104 | #if canImport(CoreImage)
105 | import CoreImage
106 | extension CIColor: DefaultRepresentable {
107 | public static var defaultValue: Self {
108 | .init()
109 | }
110 | }
111 | #endif
112 |
113 | #if canImport(SwiftUI)
114 | import SwiftUI
115 | extension Color: DefaultRepresentable {
116 | public static var defaultValue: Self {
117 | .black
118 | }
119 | }
120 | #endif
121 |
122 | extension NSUIColor: DefaultRepresentable {
123 | public static var defaultValue: Self {
124 | .init()
125 | }
126 | }
127 |
128 | extension Optional: DefaultRepresentable where Wrapped: DefaultRepresentable {
129 | public static var defaultValue: Self {
130 | Wrapped.defaultValue
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/Sources/EditValueView/Protocol/OptionalCaseIterable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OptionalCaseIterable.swift
3 | //
4 | //
5 | // Created by p-x9 on 2023/07/23.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol OptionalCaseIterable: OptionalType where Wrapped: CaseIterable {
12 | /// A type that can represent a collection of all values of this type.
13 | associatedtype OptionalAllCases: Collection = [Wrapped?] where Wrapped == Wrapped.AllCases.Element
14 |
15 | /// A collection of all values of this type.
16 | static var optionalAllCases: Self.OptionalAllCases { get }
17 | }
18 |
19 | extension OptionalCaseIterable {
20 | public static var optionalAllCases: [Wrapped?] {
21 | Array(Wrapped.allCases.map { .some($0) }) + [.none]
22 | }
23 | }
24 |
25 | extension Optional: OptionalCaseIterable where Wrapped: CaseIterable {}
26 |
--------------------------------------------------------------------------------
/Sources/EditValueView/Protocol/OptionalNumeric.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OptionalNumeric.swift
3 | //
4 | //
5 | // Created by p-x9 on 2023/07/23.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol OptionalNumeric: OptionalType where Wrapped: Numeric {}
12 |
13 | extension Optional: OptionalNumeric where Wrapped: Numeric {}
14 |
--------------------------------------------------------------------------------
/Sources/EditValueView/Protocol/OptionalType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OptionalType.swift
3 | //
4 | //
5 | // Created by p-x9 on 2023/07/23.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol OptionalType {
12 | associatedtype Wrapped
13 |
14 | var wrapped: Wrapped? { get }
15 | }
16 |
17 | extension OptionalType {
18 | static var isWrappedCaseiterable: Bool {
19 | Wrapped.self is any CaseIterable.Type
20 | }
21 | }
22 |
23 | extension Optional: OptionalType {
24 | public var wrapped: Wrapped? { self }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/EditValueView/UIKit/EditValueViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EditValueViewController.swift
3 | //
4 | //
5 | // Created by p-x9 on 2022/10/28.
6 | //
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 | import SwiftUI
12 |
13 | public class EditValueViewController: UIHostingController> {
14 |
15 | /// Name of the property to be edited
16 | /// Used for navigation titles and type descriptions.
17 | let key: String
18 |
19 | /// This is called when editing is completed by pressing the save button.
20 | public var onUpdate: ((Value) -> Void)?
21 |
22 | /// Used to perform validation checks when editing values
23 | public var validate: ((Value) -> Bool)?
24 |
25 | /// Initialize with key and value
26 | /// - Parameters:
27 | /// - key: Name of the property to be edited. Used for navigation titles and type descriptions.
28 | /// - value: Initial value of the value to be edited
29 | public init(
30 | _ target: Root,
31 | key: String,
32 | keyPath: WritableKeyPath
33 | ) {
34 | self.key = key
35 |
36 | super.init(rootView: .init(target, key: key, keyPath: keyPath))
37 |
38 | rootView = rootView
39 | .onUpdate({ newValue in
40 | self.onUpdate?(newValue)
41 | })
42 | .validate({ newValue in
43 | self.validate?(newValue) ?? true
44 | })
45 | }
46 |
47 | /// initialize with keyPath
48 | /// - Parameters:
49 | /// - target: Target object that has the property to be edited.
50 | /// - key: Name of the property to be edited. Used for navigation titles and type descriptions.
51 | /// - keyPath: keyPath of the property to be edited.
52 | public init(key: String, value: Value) {
53 | self.key = key
54 |
55 | super.init(rootView: .init(key: key, value: value))
56 |
57 | rootView = rootView
58 | .onUpdate({ newValue in
59 | self.onUpdate?(newValue)
60 | })
61 | .validate({ newValue in
62 | self.validate?(newValue) ?? true
63 | })
64 | }
65 |
66 | @available(*, unavailable)
67 | required init?(coder: NSCoder) {
68 | fatalError("init(coder:) has not been implemented")
69 | }
70 |
71 | public override func viewDidLoad() {
72 | super.viewDidLoad()
73 |
74 | let style: EditValueView.PresentationStyle
75 |
76 | if navigationController == nil {
77 | style = .modal
78 | } else {
79 | style = .push
80 | }
81 |
82 | rootView = rootView
83 | .presentationStyle(style)
84 | }
85 | }
86 |
87 | #endif
88 |
--------------------------------------------------------------------------------
/Sources/EditValueView/Util/Typealias.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Typealias.swift
3 | //
4 | //
5 | // Created by p-x9 on 2022/10/30.
6 | //
7 | //
8 |
9 | #if canImport(UIKit)
10 | import UIKit
11 | typealias NSUIColor = UIColor
12 | typealias NSUIImage = UIImage
13 | #endif
14 |
15 | #if canImport(Cocoa)
16 | import Cocoa
17 | typealias NSUIColor = NSColor
18 | typealias NSUIImage = NSImage
19 | #endif
20 |
--------------------------------------------------------------------------------
/Tests/EditValueViewTests/EditValueViewTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import EditValueView
3 |
4 | final class EditValueViewTests: XCTestCase {
5 | }
6 |
--------------------------------------------------------------------------------