├── .gitignore ├── .travis.d ├── before-install.sh └── install.sh ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── Package.swift ├── README.md ├── Samples └── RulesTestApp │ ├── RulesTestApp.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── RulesTestApp-Mobile.xcscheme │ ├── RulesTestAppMobile │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── ContentView.swift │ ├── Info.plist │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ └── SceneDelegate.swift │ └── Shared │ ├── EnvironmentKeys.swift │ ├── MainView.swift │ ├── Model.swift │ ├── RuleModel.swift │ └── RulingViews.swift ├── Sources └── SwiftUIRules │ ├── Assignments │ ├── README.md │ ├── RuleAction.swift │ ├── RuleCandidate.swift │ ├── RuleKeyAssignment.swift │ ├── RuleTypeIDAssignment.swift │ ├── RuleTypeIDPathAssignment.swift │ └── RuleValueAssignment.swift │ ├── DynamicEnvironment │ ├── DynamicEnvironmentKey.swift │ ├── DynamicEnvironmentPathes.swift │ ├── DynamicEnvironmentValues.swift │ └── README.md │ ├── Operators │ ├── AssignmentOperators.swift │ ├── CompoundPredicateOperators.swift │ ├── KeyPathPredicateOperators.swift │ ├── README.md │ └── RuleOperators.swift │ ├── Predicates │ ├── README.md │ ├── RuleBoolPredicate.swift │ ├── RuleClosurePredicate.swift │ ├── RuleCompoundPredicate.swift │ ├── RuleKeyPathPredicate.swift │ └── RulePredicate.swift │ ├── Rule.swift │ ├── RuleContext.swift │ ├── RuleModel.swift │ └── Support │ └── RuleDebug.swift ├── SwiftUIRules.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── SwiftUIRules-Mac.xcscheme │ ├── SwiftUIRules-Mobile.xcscheme │ └── SwiftUIRules-Watch.xcscheme ├── Tests └── SwiftUITests │ ├── Info.plist │ ├── SwiftUITests.swift │ └── TestEnvironmentKeys.swift └── xcconfig └── Base.xcconfig /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 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 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | 70 | # hh 71 | .DS_Store 72 | Package.resolved 73 | .docker.build 74 | 75 | -------------------------------------------------------------------------------- /.travis.d/before-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ "$TRAVIS_OS_NAME" == "Linux" ]]; then 4 | sudo apt-get install -y wget \ 5 | clang-3.6 libc6-dev make git libicu52 libicu-dev \ 6 | git autoconf libtool pkg-config \ 7 | libblocksruntime-dev \ 8 | libkqueue-dev \ 9 | libpthread-workqueue-dev \ 10 | systemtap-sdt-dev \ 11 | libbsd-dev libbsd0 libbsd0-dbg \ 12 | curl libcurl4-openssl-dev \ 13 | libedit-dev \ 14 | python2.7 python2.7-dev \ 15 | libxml2 16 | 17 | sudo update-alternatives --quiet --install /usr/bin/clang clang /usr/bin/clang-3.6 100 18 | sudo update-alternatives --quiet --install /usr/bin/clang++ clang++ /usr/bin/clang++-3.6 100 19 | fi 20 | -------------------------------------------------------------------------------- /.travis.d/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # our path is: 4 | # /home/travis/build/NozeIO/Noze.io/ 5 | 6 | if ! test -z "$SWIFT_SNAPSHOT_NAME"; then 7 | # Install Swift 8 | wget "${SWIFT_SNAPSHOT_NAME}" 9 | 10 | TARBALL="`ls swift-*.tar.gz`" 11 | echo "Tarball: $TARBALL" 12 | 13 | TARPATH="$PWD/$TARBALL" 14 | 15 | cd $HOME # expand Swift tarball in $HOME 16 | tar zx --strip 1 --file=$TARPATH 17 | pwd 18 | 19 | export PATH="$PWD/usr/bin:$PATH" 20 | which swift 21 | 22 | if [ `which swift` ]; then 23 | echo "Installed Swift: `which swift`" 24 | else 25 | echo "Failed to install Swift?" 26 | exit 42 27 | fi 28 | fi 29 | 30 | swift --version 31 | 32 | 33 | # Environment 34 | 35 | TT_SWIFT_BINARY=`which swift` 36 | 37 | echo "${TT_SWIFT_BINARY}" 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | 3 | notifications: 4 | slack: 5 | rooms: 6 | - zeeql:odi4PEJUdmDPkBfjhHIaSdrS 7 | 8 | matrix: 9 | include: 10 | - os: osx 11 | osx_image: xcode11 12 | 13 | before_install: 14 | - ./.travis.d/before-install.sh 15 | 16 | install: 17 | - ./.travis.d/install.sh 18 | 19 | script: 20 | - export PATH="$HOME/usr/bin:$PATH" 21 | - swift build -c release 22 | - swift build -c debug 23 | - xcodebuild -scheme SwiftUIRules-All -configuration Debug -target SwiftUIRules-All | xcpretty 24 | - xcodebuild -scheme SwiftUIRules-All -configuration Release -target SwiftUIRules-All | xcpretty 25 | 26 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Legal 2 | 3 | By submitting a pull request, you represent that you have the right to license 4 | your contribution to ZeeZide GmbH and the community, and agree by submitting 5 | the patch that your contributions are licensed under the Apache 2.0 license. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 ZeeZide GmbH 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | 7 | name: "SwiftUIRules", 8 | 9 | platforms: [ 10 | .macOS(.v10_15), .iOS(.v13), .watchOS(.v6) 11 | ], 12 | 13 | products: [ 14 | .library(name: "SwiftUIRules", targets: [ "SwiftUIRules" ]) 15 | ], 16 | 17 | dependencies: [], 18 | 19 | targets: [ 20 | .target(name: "SwiftUIRules", dependencies: []) 21 | ] 22 | ) 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

SwiftUI Rules 2 | 4 |

5 | 6 | ![Swift5.1](https://img.shields.io/badge/swift-5.1-blue.svg) 7 | ![macOS](https://img.shields.io/badge/os-macOS-green.svg?style=flat) 8 | ![iOS](https://img.shields.io/badge/os-iOS-green.svg?style=flat) 9 | ![watchOS](https://img.shields.io/badge/os-watchOS-green.svg?style=flat) 10 | ![Travis](https://api.travis-ci.org/DirectToSwiftUI/SwiftUIRules.svg?branch=develop&style=flat) 11 | 12 | _Going fully declarative_: SwiftUI Rules. 13 | 14 | SwiftUI Rules is covered in the companion AlwaysRightInstitute 15 | blog post: 16 | [Dynamic Environments ¶ SwiftUI Rules](http://www.alwaysrightinstitute.com/swiftuirules/). 17 | 18 | ## Requirements 19 | 20 | SwiftUI Rules requires an environment capable to run SwiftUI. 21 | That is: macOS Catalina, iOS 13 or watchOS 6. 22 | In combination w/ Xcode 11. 23 | 24 | Note that you can run iOS 13/watchOS 6 apps on Mojave in the emulator, 25 | so that is fine as well. 26 | 27 | ## Using the Package 28 | 29 | You can either just drag the SwiftUIRules Xcode project into your own 30 | project, 31 | or you can use Swift Package Manager. 32 | 33 | The package URL is: 34 | [https://github.com/DirectToSwiftUI/SwiftUIRules.git 35 | ](https://github.com/DirectToSwiftUI/SwiftUIRules.git). 36 | 37 | ## Using SwiftUI Rules 38 | 39 | SwiftUI Rules is covered in the companion AlwaysRightInstitute 40 | blog post: 41 | [Dynamic Environments ¶ SwiftUI Rules](http://www.alwaysrightinstitute.com/swiftuirules/). 42 | 43 | ### Declaring Own Environment Keys 44 | 45 | Let's say we want to add an own 46 | environment key called `fancyColor`. 47 | 48 | First thing we need is an 49 | [`DynamicEnvironmentKey`](https://github.com/DirectToSwiftUI/SwiftUIRules/blob/develop/Sources/SwiftUIRules/DynamicEnvironment/DynamicEnvironmentKey.swift#L17) 50 | declaration: 51 | ```swift 52 | struct FancyColorEnvironmentKey: DynamicEnvironmentKey { 53 | public static let defaultValue = Color.black 54 | } 55 | ``` 56 | Most importantly this specifies the static Swift type of the environment key 57 | (`Color`) 58 | and it provides a default value. 59 | That value is used when the environment key is queried, 60 | but no value has been explicitly set by the user. 61 | 62 | Second we need to declare a property on the 63 | [DynamicEnvironmentPathes](https://github.com/DirectToSwiftUI/SwiftUIRules/blob/develop/Sources/SwiftUIRules/DynamicEnvironment/DynamicEnvironmentPathes.swift#L19) 64 | struct: 65 | ```swift 66 | extension DynamicEnvironmentPathes { 67 | var fancyColor : Color { 68 | set { self[dynamic: FancyColorEnvironmentKey.self] = newValue } 69 | get { self[dynamic: FancyColorEnvironmentKey.self] } 70 | } 71 | } 72 | ``` 73 | That's it. We can start using our new key. 74 | 75 | Some View accessing our splendid new `fancyColor` 76 | using the 77 | [@Environment](https://developer.apple.com/documentation/swiftui/environment) 78 | property wrapper: 79 | ```swift 80 | struct FancyText: View { 81 | 82 | @Environment(\.fancyColor) private var color 83 | 84 | var label : String 85 | 86 | var body: some View { 87 | Text(label) 88 | .foregroundColor(color) // boooring 89 | } 90 | } 91 | ``` 92 | and a View providing it: 93 | 94 | ```swift 95 | struct MyPage: View { 96 | 97 | var body: some View { 98 | VStack { 99 | Text("Hello") 100 | FancyText("World") 101 | } 102 | .environment(\.fancyColor, .red) 103 | } 104 | } 105 | ``` 106 | 107 | ### Setting Up a Ruling Environment 108 | 109 | We recommend creating a `RuleModel.swift` Swift file. Put all your 110 | rules in that central location: 111 | ```swift 112 | // RuleModel.swift 113 | import SwiftUIRules 114 | 115 | let ruleModel : RuleModel = [ 116 | \.priority == .low => \.fancyColor <= .gray, 117 | \.priority == .high => \.fancyColor <= .red, 118 | \.priority == .normal => \.fancyColor <= .black 119 | ] 120 | ``` 121 | 122 | You can hookup the rule system at any place in the SwiftUI View hierarchy, 123 | but we again recommend to do that at the very top. 124 | For example in a fresh application generated in Xcode, you could modify 125 | the generated `ContentView` like so: 126 | ```swift 127 | struct ContentView: View { 128 | private let ruleContext = RuleContext(ruleModel: ruleModel) 129 | 130 | var body: some View { 131 | Group { 132 | // your views 133 | } 134 | .environment(\.ruleContext, ruleContext) 135 | } 136 | } 137 | ``` 138 | 139 | Quite often some “root” properties need to be injected: 140 | ```swift 141 | struct TodoList: View { 142 | let todos: [ Todo ] 143 | 144 | var body: someView { 145 | VStack { 146 | Text("Todos:") 147 | ForEach(todos) { todo in 148 | TodoView() 149 | // make todo available to the rule system 150 | .environment(\.todo, todo) 151 | } 152 | } 153 | } 154 | } 155 | ``` 156 | `TodoView` and child views of that can now derive environment values of 157 | the `todo` key using the rule system. 158 | 159 | ### Use Cases 160 | 161 | Ha! Endless 🤓 It is quite different to “Think In Rules”™ 162 | (a.k.a. declaratively), 163 | but they allow you to compose your application in a highly decoupled 164 | and actually “declarative” ways. 165 | 166 | It can be used low level, kinda like CSS. 167 | Consider dynamic environment keys a little like CSS classes. 168 | E.g. you could switch settings based on the platform: 169 | ```swift 170 | [ 171 | \.platform == "watch" => \.details <= "minimal", 172 | \.platform == "phone" => \.details <= "regular", 173 | \.platform == "mac" || \.platform == "pad" 174 | => \.details <= "high" 175 | ] 176 | ``` 177 | 178 | But it can also be used at a very high level, 179 | for example in a workflow system: 180 | ```swift 181 | [ 182 | \.task.status == "done" => \.view <= TaskFinishedView(), 183 | \.task.status == "done" => \.actions <= [], 184 | \.task.status == "created" => \.view <= NewTaskView(), 185 | \.task.status == "created" => \.actions = [ .accept, .reject ] 186 | ] 187 | 188 | struct TaskView: View { 189 | @Environment(\.view) var body // body derived from rules 190 | } 191 | ``` 192 | 193 | Since SwiftUI Views are also just lightweight structs, 194 | you can build dynamic properties which carry them! 195 | 196 | In any case: We are interested in any idea how to use it! 197 | 198 | 199 | ### Limitations 200 | 201 | #### Only `DynamicEnvironmentKey`s 202 | 203 | Currently rules can only evaluate `DynamicEnvironmentKey`s, 204 | it doesn't take regular environment keys into account. 205 | That is, you can't drive for example the builtin SwiftUI `lineLimit` 206 | using the rulesystem. 207 | ```swift 208 | [ 209 | \.user.status == "VIP" => \.lineLimit <= 10, 210 | \.lineLimit <= 2 211 | ] 212 | ``` 213 | **Does not work**. This is currently made explicit by requiring keys which 214 | are used w/ the system to have the `DynamicEnvironmentKey` type. 215 | SO you can't accidentially run into this. 216 | 217 | We may open it up to any `EnvironmentKey`, TBD. 218 | 219 | #### No KeyPath'es in Assignments 220 | 221 | Sometimes one might want this: 222 | ```swift 223 | \.todos.count > 10 => \.person.status <= "VIP" 224 | ``` 225 | I.e. assign a value to a multi component keypath (`\.person.status`). 226 | That **does not work**. 227 | 228 | #### SwiftUI Bugs 229 | 230 | Sometimes SwiftUI “looses” its environment during navigation or in List's. 231 | watchOS and macOS seem to be particularily problematic, iOS less so. 232 | If that happens, one can pass on the `ruleContext` manually: 233 | ```swift 234 | struct MyNavLink: View { 235 | @Environment(\.ruleContext) var ruleContext 236 | ... 237 | var body: someView { 238 | NavLink(destination: destination 239 | // Explicitly pass along: 240 | .environment(\.ruleContext, ruleContext)) 241 | ... 242 | } 243 | ``` 244 | 245 | 246 | ## Who 247 | 248 | Brought to you by 249 | [The Always Right Institute](http://www.alwaysrightinstitute.com) 250 | and 251 | [ZeeZide](http://zeezide.de). 252 | We like 253 | [feedback](https://twitter.com/ar_institute), 254 | GitHub stars, 255 | cool [contract work](http://zeezide.com/en/services/services.html), 256 | presumably any form of praise you can think of. 257 | -------------------------------------------------------------------------------- /Samples/RulesTestApp/RulesTestApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E87A576C23196788004EFF40 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87A576B23196788004EFF40 /* AppDelegate.swift */; }; 11 | E87A576E23196788004EFF40 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87A576D23196788004EFF40 /* SceneDelegate.swift */; }; 12 | E87A577023196788004EFF40 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87A576F23196788004EFF40 /* ContentView.swift */; }; 13 | E87A577223196789004EFF40 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E87A577123196789004EFF40 /* Assets.xcassets */; }; 14 | E87A577523196789004EFF40 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E87A577423196789004EFF40 /* Preview Assets.xcassets */; }; 15 | E87A577823196789004EFF40 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E87A577623196789004EFF40 /* LaunchScreen.storyboard */; }; 16 | E87A57912319695C004EFF40 /* RuleModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87A57902319695C004EFF40 /* RuleModel.swift */; }; 17 | E87A57952319696A004EFF40 /* libSwiftUIRules-Mobile.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E87A578A23196859004EFF40 /* libSwiftUIRules-Mobile.a */; }; 18 | E87A57972319698E004EFF40 /* EnvironmentKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87A57962319698E004EFF40 /* EnvironmentKeys.swift */; }; 19 | E87A579B231969FD004EFF40 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87A579A231969FD004EFF40 /* Model.swift */; }; 20 | E87A579D23196AA1004EFF40 /* RulingViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87A579C23196AA1004EFF40 /* RulingViews.swift */; }; 21 | E87A579F23196D35004EFF40 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87A579E23196D35004EFF40 /* MainView.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXContainerItemProxy section */ 25 | E87A578723196859004EFF40 /* PBXContainerItemProxy */ = { 26 | isa = PBXContainerItemProxy; 27 | containerPortal = E87A577F23196859004EFF40 /* SwiftUIRules.xcodeproj */; 28 | proxyType = 2; 29 | remoteGlobalIDString = E8FA13C9231929B40055A9F9; 30 | remoteInfo = "SwiftUIRules-Mac"; 31 | }; 32 | E87A578923196859004EFF40 /* PBXContainerItemProxy */ = { 33 | isa = PBXContainerItemProxy; 34 | containerPortal = E87A577F23196859004EFF40 /* SwiftUIRules.xcodeproj */; 35 | proxyType = 2; 36 | remoteGlobalIDString = E8A02CDD231948FE00C1D879; 37 | remoteInfo = "SwiftUIRules-Mobile"; 38 | }; 39 | E87A578B23196859004EFF40 /* PBXContainerItemProxy */ = { 40 | isa = PBXContainerItemProxy; 41 | containerPortal = E87A577F23196859004EFF40 /* SwiftUIRules.xcodeproj */; 42 | proxyType = 2; 43 | remoteGlobalIDString = E8A02CF92319493D00C1D879; 44 | remoteInfo = "SwiftUIRules-Watch"; 45 | }; 46 | E87A578D23196859004EFF40 /* PBXContainerItemProxy */ = { 47 | isa = PBXContainerItemProxy; 48 | containerPortal = E87A577F23196859004EFF40 /* SwiftUIRules.xcodeproj */; 49 | proxyType = 2; 50 | remoteGlobalIDString = E84699C323194CA800794065; 51 | remoteInfo = SwiftUITests; 52 | }; 53 | E87A579223196966004EFF40 /* PBXContainerItemProxy */ = { 54 | isa = PBXContainerItemProxy; 55 | containerPortal = E87A577F23196859004EFF40 /* SwiftUIRules.xcodeproj */; 56 | proxyType = 1; 57 | remoteGlobalIDString = E8A02CC2231948FE00C1D879; 58 | remoteInfo = "SwiftUIRules-Mobile"; 59 | }; 60 | /* End PBXContainerItemProxy section */ 61 | 62 | /* Begin PBXFileReference section */ 63 | E87A576823196788004EFF40 /* RulesTestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RulesTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | E87A576B23196788004EFF40 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 65 | E87A576D23196788004EFF40 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 66 | E87A576F23196788004EFF40 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 67 | E87A577123196789004EFF40 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 68 | E87A577423196789004EFF40 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 69 | E87A577723196789004EFF40 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 70 | E87A577923196789004EFF40 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 71 | E87A577F23196859004EFF40 /* SwiftUIRules.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SwiftUIRules.xcodeproj; path = ../../SwiftUIRules.xcodeproj; sourceTree = ""; }; 72 | E87A57902319695C004EFF40 /* RuleModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleModel.swift; sourceTree = ""; }; 73 | E87A57962319698E004EFF40 /* EnvironmentKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentKeys.swift; sourceTree = ""; }; 74 | E87A579A231969FD004EFF40 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = ""; }; 75 | E87A579C23196AA1004EFF40 /* RulingViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RulingViews.swift; sourceTree = ""; }; 76 | E87A579E23196D35004EFF40 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; 77 | /* End PBXFileReference section */ 78 | 79 | /* Begin PBXFrameworksBuildPhase section */ 80 | E87A576523196788004EFF40 /* Frameworks */ = { 81 | isa = PBXFrameworksBuildPhase; 82 | buildActionMask = 2147483647; 83 | files = ( 84 | E87A57952319696A004EFF40 /* libSwiftUIRules-Mobile.a in Frameworks */, 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | /* End PBXFrameworksBuildPhase section */ 89 | 90 | /* Begin PBXGroup section */ 91 | E87A575F23196788004EFF40 = { 92 | isa = PBXGroup; 93 | children = ( 94 | E87A578F23196943004EFF40 /* Shared */, 95 | E87A576A23196788004EFF40 /* RulesTestAppMobile */, 96 | E87A577F23196859004EFF40 /* SwiftUIRules.xcodeproj */, 97 | E87A576923196788004EFF40 /* Products */, 98 | E87A57942319696A004EFF40 /* Frameworks */, 99 | ); 100 | sourceTree = ""; 101 | }; 102 | E87A576923196788004EFF40 /* Products */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | E87A576823196788004EFF40 /* RulesTestApp.app */, 106 | ); 107 | name = Products; 108 | sourceTree = ""; 109 | }; 110 | E87A576A23196788004EFF40 /* RulesTestAppMobile */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | E87A576F23196788004EFF40 /* ContentView.swift */, 114 | E87A576B23196788004EFF40 /* AppDelegate.swift */, 115 | E87A576D23196788004EFF40 /* SceneDelegate.swift */, 116 | E87A577123196789004EFF40 /* Assets.xcassets */, 117 | E87A577623196789004EFF40 /* LaunchScreen.storyboard */, 118 | E87A577923196789004EFF40 /* Info.plist */, 119 | E87A577323196789004EFF40 /* Preview Content */, 120 | ); 121 | path = RulesTestAppMobile; 122 | sourceTree = ""; 123 | }; 124 | E87A577323196789004EFF40 /* Preview Content */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | E87A577423196789004EFF40 /* Preview Assets.xcassets */, 128 | ); 129 | path = "Preview Content"; 130 | sourceTree = ""; 131 | }; 132 | E87A578023196859004EFF40 /* Products */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | E87A578823196859004EFF40 /* libSwiftUIRules-Mac.a */, 136 | E87A578A23196859004EFF40 /* libSwiftUIRules-Mobile.a */, 137 | E87A578C23196859004EFF40 /* libSwiftUIRules-Watch.a */, 138 | E87A578E23196859004EFF40 /* SwiftUITests.xctest */, 139 | ); 140 | name = Products; 141 | sourceTree = ""; 142 | }; 143 | E87A578F23196943004EFF40 /* Shared */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | E87A57902319695C004EFF40 /* RuleModel.swift */, 147 | E87A579E23196D35004EFF40 /* MainView.swift */, 148 | E87A579C23196AA1004EFF40 /* RulingViews.swift */, 149 | E87A579A231969FD004EFF40 /* Model.swift */, 150 | E87A57962319698E004EFF40 /* EnvironmentKeys.swift */, 151 | ); 152 | path = Shared; 153 | sourceTree = ""; 154 | }; 155 | E87A57942319696A004EFF40 /* Frameworks */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | ); 159 | name = Frameworks; 160 | sourceTree = ""; 161 | }; 162 | /* End PBXGroup section */ 163 | 164 | /* Begin PBXNativeTarget section */ 165 | E87A576723196788004EFF40 /* RulesTestApp-Mobile */ = { 166 | isa = PBXNativeTarget; 167 | buildConfigurationList = E87A577C23196789004EFF40 /* Build configuration list for PBXNativeTarget "RulesTestApp-Mobile" */; 168 | buildPhases = ( 169 | E87A576423196788004EFF40 /* Sources */, 170 | E87A576523196788004EFF40 /* Frameworks */, 171 | E87A576623196788004EFF40 /* Resources */, 172 | ); 173 | buildRules = ( 174 | ); 175 | dependencies = ( 176 | E87A579323196966004EFF40 /* PBXTargetDependency */, 177 | ); 178 | name = "RulesTestApp-Mobile"; 179 | productName = RulesTestApp; 180 | productReference = E87A576823196788004EFF40 /* RulesTestApp.app */; 181 | productType = "com.apple.product-type.application"; 182 | }; 183 | /* End PBXNativeTarget section */ 184 | 185 | /* Begin PBXProject section */ 186 | E87A576023196788004EFF40 /* Project object */ = { 187 | isa = PBXProject; 188 | attributes = { 189 | LastSwiftUpdateCheck = 1100; 190 | LastUpgradeCheck = 1100; 191 | ORGANIZATIONNAME = "ZeeZide GmbH"; 192 | TargetAttributes = { 193 | E87A576723196788004EFF40 = { 194 | CreatedOnToolsVersion = 11.0; 195 | }; 196 | }; 197 | }; 198 | buildConfigurationList = E87A576323196788004EFF40 /* Build configuration list for PBXProject "RulesTestApp" */; 199 | compatibilityVersion = "Xcode 9.3"; 200 | developmentRegion = en; 201 | hasScannedForEncodings = 0; 202 | knownRegions = ( 203 | en, 204 | Base, 205 | ); 206 | mainGroup = E87A575F23196788004EFF40; 207 | productRefGroup = E87A576923196788004EFF40 /* Products */; 208 | projectDirPath = ""; 209 | projectReferences = ( 210 | { 211 | ProductGroup = E87A578023196859004EFF40 /* Products */; 212 | ProjectRef = E87A577F23196859004EFF40 /* SwiftUIRules.xcodeproj */; 213 | }, 214 | ); 215 | projectRoot = ""; 216 | targets = ( 217 | E87A576723196788004EFF40 /* RulesTestApp-Mobile */, 218 | ); 219 | }; 220 | /* End PBXProject section */ 221 | 222 | /* Begin PBXReferenceProxy section */ 223 | E87A578823196859004EFF40 /* libSwiftUIRules-Mac.a */ = { 224 | isa = PBXReferenceProxy; 225 | fileType = archive.ar; 226 | path = "libSwiftUIRules-Mac.a"; 227 | remoteRef = E87A578723196859004EFF40 /* PBXContainerItemProxy */; 228 | sourceTree = BUILT_PRODUCTS_DIR; 229 | }; 230 | E87A578A23196859004EFF40 /* libSwiftUIRules-Mobile.a */ = { 231 | isa = PBXReferenceProxy; 232 | fileType = archive.ar; 233 | path = "libSwiftUIRules-Mobile.a"; 234 | remoteRef = E87A578923196859004EFF40 /* PBXContainerItemProxy */; 235 | sourceTree = BUILT_PRODUCTS_DIR; 236 | }; 237 | E87A578C23196859004EFF40 /* libSwiftUIRules-Watch.a */ = { 238 | isa = PBXReferenceProxy; 239 | fileType = archive.ar; 240 | path = "libSwiftUIRules-Watch.a"; 241 | remoteRef = E87A578B23196859004EFF40 /* PBXContainerItemProxy */; 242 | sourceTree = BUILT_PRODUCTS_DIR; 243 | }; 244 | E87A578E23196859004EFF40 /* SwiftUITests.xctest */ = { 245 | isa = PBXReferenceProxy; 246 | fileType = wrapper.cfbundle; 247 | path = SwiftUITests.xctest; 248 | remoteRef = E87A578D23196859004EFF40 /* PBXContainerItemProxy */; 249 | sourceTree = BUILT_PRODUCTS_DIR; 250 | }; 251 | /* End PBXReferenceProxy section */ 252 | 253 | /* Begin PBXResourcesBuildPhase section */ 254 | E87A576623196788004EFF40 /* Resources */ = { 255 | isa = PBXResourcesBuildPhase; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | E87A577823196789004EFF40 /* LaunchScreen.storyboard in Resources */, 259 | E87A577523196789004EFF40 /* Preview Assets.xcassets in Resources */, 260 | E87A577223196789004EFF40 /* Assets.xcassets in Resources */, 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | }; 264 | /* End PBXResourcesBuildPhase section */ 265 | 266 | /* Begin PBXSourcesBuildPhase section */ 267 | E87A576423196788004EFF40 /* Sources */ = { 268 | isa = PBXSourcesBuildPhase; 269 | buildActionMask = 2147483647; 270 | files = ( 271 | E87A579B231969FD004EFF40 /* Model.swift in Sources */, 272 | E87A57972319698E004EFF40 /* EnvironmentKeys.swift in Sources */, 273 | E87A579F23196D35004EFF40 /* MainView.swift in Sources */, 274 | E87A576C23196788004EFF40 /* AppDelegate.swift in Sources */, 275 | E87A579D23196AA1004EFF40 /* RulingViews.swift in Sources */, 276 | E87A576E23196788004EFF40 /* SceneDelegate.swift in Sources */, 277 | E87A577023196788004EFF40 /* ContentView.swift in Sources */, 278 | E87A57912319695C004EFF40 /* RuleModel.swift in Sources */, 279 | ); 280 | runOnlyForDeploymentPostprocessing = 0; 281 | }; 282 | /* End PBXSourcesBuildPhase section */ 283 | 284 | /* Begin PBXTargetDependency section */ 285 | E87A579323196966004EFF40 /* PBXTargetDependency */ = { 286 | isa = PBXTargetDependency; 287 | name = "SwiftUIRules-Mobile"; 288 | targetProxy = E87A579223196966004EFF40 /* PBXContainerItemProxy */; 289 | }; 290 | /* End PBXTargetDependency section */ 291 | 292 | /* Begin PBXVariantGroup section */ 293 | E87A577623196789004EFF40 /* LaunchScreen.storyboard */ = { 294 | isa = PBXVariantGroup; 295 | children = ( 296 | E87A577723196789004EFF40 /* Base */, 297 | ); 298 | name = LaunchScreen.storyboard; 299 | sourceTree = ""; 300 | }; 301 | /* End PBXVariantGroup section */ 302 | 303 | /* Begin XCBuildConfiguration section */ 304 | E87A577A23196789004EFF40 /* Debug */ = { 305 | isa = XCBuildConfiguration; 306 | buildSettings = { 307 | ALWAYS_SEARCH_USER_PATHS = NO; 308 | CLANG_ANALYZER_NONNULL = YES; 309 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 310 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 311 | CLANG_CXX_LIBRARY = "libc++"; 312 | CLANG_ENABLE_MODULES = YES; 313 | CLANG_ENABLE_OBJC_ARC = YES; 314 | CLANG_ENABLE_OBJC_WEAK = YES; 315 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 316 | CLANG_WARN_BOOL_CONVERSION = YES; 317 | CLANG_WARN_COMMA = YES; 318 | CLANG_WARN_CONSTANT_CONVERSION = YES; 319 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 320 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 321 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 322 | CLANG_WARN_EMPTY_BODY = YES; 323 | CLANG_WARN_ENUM_CONVERSION = YES; 324 | CLANG_WARN_INFINITE_RECURSION = YES; 325 | CLANG_WARN_INT_CONVERSION = YES; 326 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 327 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 328 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 329 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 330 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 331 | CLANG_WARN_STRICT_PROTOTYPES = YES; 332 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 333 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 334 | CLANG_WARN_UNREACHABLE_CODE = YES; 335 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 336 | COPY_PHASE_STRIP = NO; 337 | DEBUG_INFORMATION_FORMAT = dwarf; 338 | ENABLE_STRICT_OBJC_MSGSEND = YES; 339 | ENABLE_TESTABILITY = YES; 340 | GCC_C_LANGUAGE_STANDARD = gnu11; 341 | GCC_DYNAMIC_NO_PIC = NO; 342 | GCC_NO_COMMON_BLOCKS = YES; 343 | GCC_OPTIMIZATION_LEVEL = 0; 344 | GCC_PREPROCESSOR_DEFINITIONS = ( 345 | "DEBUG=1", 346 | "$(inherited)", 347 | ); 348 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 349 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 350 | GCC_WARN_UNDECLARED_SELECTOR = YES; 351 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 352 | GCC_WARN_UNUSED_FUNCTION = YES; 353 | GCC_WARN_UNUSED_VARIABLE = YES; 354 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 355 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 356 | MTL_FAST_MATH = YES; 357 | ONLY_ACTIVE_ARCH = YES; 358 | SDKROOT = iphoneos; 359 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 360 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 361 | }; 362 | name = Debug; 363 | }; 364 | E87A577B23196789004EFF40 /* Release */ = { 365 | isa = XCBuildConfiguration; 366 | buildSettings = { 367 | ALWAYS_SEARCH_USER_PATHS = NO; 368 | CLANG_ANALYZER_NONNULL = YES; 369 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 370 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 371 | CLANG_CXX_LIBRARY = "libc++"; 372 | CLANG_ENABLE_MODULES = YES; 373 | CLANG_ENABLE_OBJC_ARC = YES; 374 | CLANG_ENABLE_OBJC_WEAK = YES; 375 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 376 | CLANG_WARN_BOOL_CONVERSION = YES; 377 | CLANG_WARN_COMMA = YES; 378 | CLANG_WARN_CONSTANT_CONVERSION = YES; 379 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 380 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 381 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 382 | CLANG_WARN_EMPTY_BODY = YES; 383 | CLANG_WARN_ENUM_CONVERSION = YES; 384 | CLANG_WARN_INFINITE_RECURSION = YES; 385 | CLANG_WARN_INT_CONVERSION = YES; 386 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 387 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 388 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 389 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 390 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 391 | CLANG_WARN_STRICT_PROTOTYPES = YES; 392 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 393 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 394 | CLANG_WARN_UNREACHABLE_CODE = YES; 395 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 396 | COPY_PHASE_STRIP = NO; 397 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 398 | ENABLE_NS_ASSERTIONS = NO; 399 | ENABLE_STRICT_OBJC_MSGSEND = YES; 400 | GCC_C_LANGUAGE_STANDARD = gnu11; 401 | GCC_NO_COMMON_BLOCKS = YES; 402 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 403 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 404 | GCC_WARN_UNDECLARED_SELECTOR = YES; 405 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 406 | GCC_WARN_UNUSED_FUNCTION = YES; 407 | GCC_WARN_UNUSED_VARIABLE = YES; 408 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 409 | MTL_ENABLE_DEBUG_INFO = NO; 410 | MTL_FAST_MATH = YES; 411 | SDKROOT = iphoneos; 412 | SWIFT_COMPILATION_MODE = wholemodule; 413 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 414 | VALIDATE_PRODUCT = YES; 415 | }; 416 | name = Release; 417 | }; 418 | E87A577D23196789004EFF40 /* Debug */ = { 419 | isa = XCBuildConfiguration; 420 | buildSettings = { 421 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 422 | CODE_SIGN_STYLE = Automatic; 423 | DEVELOPMENT_ASSET_PATHS = "\"RulesTestAppMobile/Preview Content\""; 424 | ENABLE_PREVIEWS = YES; 425 | INFOPLIST_FILE = RulesTestAppMobile/Info.plist; 426 | LD_RUNPATH_SEARCH_PATHS = ( 427 | "$(inherited)", 428 | "@executable_path/Frameworks", 429 | ); 430 | PRODUCT_BUNDLE_IDENTIFIER = de.zeezide.swiftui.rules.RulesTestApp.mobile; 431 | PRODUCT_NAME = RulesTestApp; 432 | SWIFT_VERSION = 5.0; 433 | TARGETED_DEVICE_FAMILY = "1,2"; 434 | }; 435 | name = Debug; 436 | }; 437 | E87A577E23196789004EFF40 /* Release */ = { 438 | isa = XCBuildConfiguration; 439 | buildSettings = { 440 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 441 | CODE_SIGN_STYLE = Automatic; 442 | DEVELOPMENT_ASSET_PATHS = "\"RulesTestAppMobile/Preview Content\""; 443 | ENABLE_PREVIEWS = YES; 444 | INFOPLIST_FILE = RulesTestAppMobile/Info.plist; 445 | LD_RUNPATH_SEARCH_PATHS = ( 446 | "$(inherited)", 447 | "@executable_path/Frameworks", 448 | ); 449 | PRODUCT_BUNDLE_IDENTIFIER = de.zeezide.swiftui.rules.RulesTestApp.mobile; 450 | PRODUCT_NAME = RulesTestApp; 451 | SWIFT_VERSION = 5.0; 452 | TARGETED_DEVICE_FAMILY = "1,2"; 453 | }; 454 | name = Release; 455 | }; 456 | /* End XCBuildConfiguration section */ 457 | 458 | /* Begin XCConfigurationList section */ 459 | E87A576323196788004EFF40 /* Build configuration list for PBXProject "RulesTestApp" */ = { 460 | isa = XCConfigurationList; 461 | buildConfigurations = ( 462 | E87A577A23196789004EFF40 /* Debug */, 463 | E87A577B23196789004EFF40 /* Release */, 464 | ); 465 | defaultConfigurationIsVisible = 0; 466 | defaultConfigurationName = Release; 467 | }; 468 | E87A577C23196789004EFF40 /* Build configuration list for PBXNativeTarget "RulesTestApp-Mobile" */ = { 469 | isa = XCConfigurationList; 470 | buildConfigurations = ( 471 | E87A577D23196789004EFF40 /* Debug */, 472 | E87A577E23196789004EFF40 /* Release */, 473 | ); 474 | defaultConfigurationIsVisible = 0; 475 | defaultConfigurationName = Release; 476 | }; 477 | /* End XCConfigurationList section */ 478 | }; 479 | rootObject = E87A576023196788004EFF40 /* Project object */; 480 | } 481 | -------------------------------------------------------------------------------- /Samples/RulesTestApp/RulesTestApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Samples/RulesTestApp/RulesTestApp.xcodeproj/xcshareddata/xcschemes/RulesTestApp-Mobile.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Samples/RulesTestApp/RulesTestAppMobile/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RulesTestApp 4 | // 5 | // Created by Helge Heß on 30.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | func application(_ application: UIApplication, 15 | didFinishLaunchingWithOptions 16 | launchOptions: [UIApplication.LaunchOptionsKey: Any]?) 17 | -> Bool 18 | { 19 | return true 20 | } 21 | 22 | // MARK: UISceneSession Lifecycle 23 | 24 | func application(_ application: UIApplication, 25 | configurationForConnecting 26 | connectingSceneSession: UISceneSession, 27 | options: UIScene.ConnectionOptions) -> UISceneConfiguration 28 | { 29 | return UISceneConfiguration(name: "Default Configuration", 30 | sessionRole: connectingSceneSession.role) 31 | } 32 | 33 | func application(_ application: UIApplication, 34 | didDiscardSceneSessions sceneSessions: Set) 35 | { 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Samples/RulesTestApp/RulesTestAppMobile/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Samples/RulesTestApp/RulesTestAppMobile/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Samples/RulesTestApp/RulesTestAppMobile/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Samples/RulesTestApp/RulesTestAppMobile/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // RulesTestApp 4 | // 5 | // Created by Helge Heß on 30.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ContentView: View { 12 | var body: some View { 13 | MainView() 14 | } 15 | } 16 | 17 | struct ContentView_Previews: PreviewProvider { 18 | static var previews: some View { 19 | ContentView() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Samples/RulesTestApp/RulesTestAppMobile/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Samples/RulesTestApp/RulesTestAppMobile/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Samples/RulesTestApp/RulesTestAppMobile/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // RulesTestApp 4 | // 5 | // Created by Helge Heß on 30.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import class SwiftUI.UIHostingController 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, 17 | options connectionOptions: UIScene.ConnectionOptions) 18 | { 19 | // Create the SwiftUI view that provides the window contents. 20 | let contentView = ContentView() 21 | 22 | // Use a UIHostingController as window root view controller. 23 | if let windowScene = scene as? UIWindowScene { 24 | let window = UIWindow(windowScene: windowScene) 25 | window.rootViewController = UIHostingController(rootView: contentView) 26 | self.window = window 27 | window.makeKeyAndVisible() 28 | } 29 | } 30 | 31 | func sceneDidDisconnect (_ scene: UIScene) {} 32 | func sceneDidBecomeActive (_ scene: UIScene) {} 33 | func sceneWillResignActive (_ scene: UIScene) {} 34 | func sceneWillEnterForeground(_ scene: UIScene) {} 35 | func sceneDidEnterBackground (_ scene: UIScene) {} 36 | } 37 | -------------------------------------------------------------------------------- /Samples/RulesTestApp/Shared/EnvironmentKeys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnvironmentKeys.swift 3 | // RulesTestApp-Mobile 4 | // 5 | // Created by Helge Heß on 30.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import SwiftUIRules 10 | import SwiftUI 11 | 12 | extension DynamicEnvironmentPathes { 13 | 14 | var title : String { 15 | set { self[dynamic: TitleEnvironmentKey.self] = newValue } 16 | get { self[dynamic: TitleEnvironmentKey.self] } 17 | } 18 | var navigationBarTitle : String { 19 | set { self[dynamic: NavigationBarTitleEnvironmentKey.self] = newValue } 20 | get { self[dynamic: NavigationBarTitleEnvironmentKey.self] } 21 | } 22 | 23 | var priority: Priority { 24 | set { self[dynamic: PriorityEnvironmentKey.self] = newValue } 25 | get { self[dynamic: PriorityEnvironmentKey.self] } 26 | } 27 | 28 | var fancyColor : Color { 29 | set { self[dynamic: FancyColorEnvironmentKey.self] = newValue } 30 | get { self[dynamic: FancyColorEnvironmentKey.self] } 31 | } 32 | 33 | var todo : Todo { 34 | set { self[dynamic: TodoEnvironmentKey.self] = newValue } 35 | get { self[dynamic: TodoEnvironmentKey.self] } 36 | } 37 | } 38 | 39 | struct TitleEnvironmentKey: DynamicEnvironmentKey { 40 | public static let defaultValue = "Hello World" 41 | } 42 | 43 | struct NavigationBarTitleEnvironmentKey: DynamicEnvironmentKey { 44 | public static let defaultValue = TitleEnvironmentKey.defaultValue 45 | } 46 | 47 | struct PriorityEnvironmentKey: DynamicEnvironmentKey { 48 | public static let defaultValue = Priority.normal 49 | } 50 | 51 | struct FancyColorEnvironmentKey: DynamicEnvironmentKey { 52 | public static let defaultValue = Color.black 53 | } 54 | 55 | struct TodoEnvironmentKey: DynamicEnvironmentKey { 56 | public static let defaultValue = 57 | Todo(title: "", completed: false, priority: .normal) 58 | } 59 | -------------------------------------------------------------------------------- /Samples/RulesTestApp/Shared/MainView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodoView.swift 3 | // RulesTestApp-Mobile 4 | // 5 | // Created by Helge Heß on 30.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import SwiftUIRules 11 | 12 | struct MainView: View { 13 | 14 | let ruleContext = RuleContext(ruleModel: ruleModel) 15 | 16 | var body: some View { 17 | VStack { 18 | ColoredTitle() 19 | .environment(\.priority, .high) 20 | .font(.title) 21 | 22 | Spacer() 23 | 24 | TodoView() 25 | .environment(\.todo, .buyBeer) // make todo available to rulesystem 26 | TodoView() 27 | .environment(\.todo, .cleanFlat) 28 | 29 | Spacer() 30 | } 31 | .environment(\.ruleContext, ruleContext) 32 | // here we inject our ruling context into all child views 33 | } 34 | 35 | } 36 | 37 | struct TodoView: View { 38 | 39 | // Note: This doesn't even know about the todo 40 | 41 | var body: some View { 42 | VStack { 43 | Text("My todo:") 44 | 45 | ColoredTitle() 46 | .font(.title) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Samples/RulesTestApp/Shared/Model.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Model.swift 3 | // RulesTestApp-Mobile 4 | // 5 | // Created by Helge Heß on 30.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | enum Priority { 10 | case low, normal, high 11 | } 12 | 13 | struct Todo { 14 | var title : String 15 | var completed : Bool 16 | var priority : Priority 17 | } 18 | 19 | extension Todo { 20 | static let buyBeer = 21 | Todo(title: "Buy beer", completed: false, priority: .high) 22 | 23 | static let cleanFlat = 24 | Todo(title: "Clean flat", completed: false, priority: .low) 25 | } 26 | -------------------------------------------------------------------------------- /Samples/RulesTestApp/Shared/RuleModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuleModel.swift 3 | // RulesTestApp-Mobile 4 | // 5 | // Created by Helge Heß on 30.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import SwiftUIRules 10 | 11 | let ruleModel : RuleModel = [ 12 | /* 13 | | predicate |=>| env-key |<=| value if predicate matches | 14 | */ 15 | \.todo.title != "" => \.priority <= \.todo.priority, 16 | \.todo.title != "" => \.title <= \.todo.title, 17 | \.title <= "Swift Rulez", 18 | 19 | \.priority == .low => \.fancyColor <= .gray, 20 | \.priority == .high => \.fancyColor <= .red, 21 | \.priority == .normal => \.fancyColor <= .black 22 | ] 23 | -------------------------------------------------------------------------------- /Samples/RulesTestApp/Shared/RulingViews.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RulingViews.swift 3 | // RulesTestApp-Mobile 4 | // 5 | // Created by Helge Heß on 30.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | // Notice how `SwiftUIRules` is irrelevant to those views, they access the 10 | // rule system as if it is an environment variable. 11 | import SwiftUI 12 | 13 | /** 14 | * This is a nonsensical view to demonstrate the rule system. 15 | * 16 | * It expects a `fancyColor` in the environment and will apply that to the 17 | * Text's foregroundColor. 18 | * 19 | * Now the trick is that the color can be generated using the rule system. 20 | */ 21 | struct ColoredText: View { 22 | 23 | var label : String 24 | 25 | @Environment(\.fancyColor) private var color 26 | 27 | var body: some View { 28 | Text(label) 29 | .foregroundColor(color) 30 | } 31 | } 32 | 33 | /** 34 | * This is a nonsensical view to demonstrate the rule system. 35 | * 36 | * This one expects a title from the environment, which again can be supplied 37 | * by the rule system. 38 | * 39 | * Notice how this one is only interested in the title. How this is colored 40 | * is transparent to the view itself. 41 | */ 42 | struct ColoredTitle: View { 43 | 44 | @Environment(\.title) private var title 45 | 46 | var body: some View { 47 | ColoredText(label: title) 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Assignments/README.md: -------------------------------------------------------------------------------- 1 |

SwiftUI Rule Assignments 2 | 4 |

5 | 6 | The `RuleAction`, most commonly a `RuleAssignment` defines what gets executed when 7 | a rule was selected for evaluation. 8 | If the rule was the best condidate for a given query, the framework will call the 9 | ```swift 10 | func fireInContext(_ context: RuleContext) -> Any? 11 | ``` 12 | method on the action. 13 | 14 | Sample: 15 | ```swift 16 | \.bannerColor <= "red" 17 | \.bannerColor <= \.defaultColor 18 | ``` 19 | 20 | Note that the name `Action` does not imply that the object somehow modifies 21 | state in the context, *it usually does not have any sideeffects*. 22 | Instead the action just *returns* the value for a requested key by performing some 23 | evaluation/calculation. 24 | 25 | The most common assignments are: 26 | - `RuleTypeIDAssignment`, return a constant value 27 | - `RuleTypeIDPathAssignment`, return the value of another key 28 | as shown in the sample above. 29 | 30 | Note: There is a glitch in the current implementation which allows you to specify 31 | full keypathes on the right hand side: 32 | ```swift 33 | \.person.firstname <= "Mickey" 34 | ``` 35 | This does not currently work. Only keypathes pointing to `DynamicEnvironmentKeys` 36 | will work. 37 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Assignments/RuleAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuleAction.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 23.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public protocol RuleAction { 10 | 11 | func fireInContext(_ context: RuleContext) -> Any? 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Assignments/RuleCandidate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuleCandidate.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 23.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * Used to determine candidates for a key resolution. 11 | * 12 | * Implemented by the assignment classes. 13 | */ 14 | public protocol RuleCandidate { 15 | func isCandidateForKey(_ key: K.Type) -> Bool 16 | 17 | // hacky, to support adding generic rules into the OID cache of the model 18 | var candidateKeyType: ObjectIdentifier { get } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Assignments/RuleKeyAssignment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuleKeyAssignment.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 23.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * RuleKeyAssignment 11 | * 12 | * This RuleAction object evaluates the action value as a lookup against the 13 | * _context_. Which then can trigger recursive rule evaluation (if the queried 14 | * key is itself a rule based value). 15 | * 16 | * In a model it looks like: 17 | *
  user.role = 'Manager' => bannerColor = defaultColor
18 | * 19 | * The bannerColor = defaultColor represents the RuleKeyAssignment. 20 | * When executed, it will query the RuleContext for the 'defaultColor' and 21 | * will return that in fireInContext(). 22 | *

23 | * Note that this object does *not* perform a 24 | * takeValueForKey(value, 'bannerColor'). It simply returns the value in 25 | * fireInContext() for further processing at upper layers. 26 | * 27 | * @see RuleAction 28 | * @see RuleAssignment 29 | */ 30 | public struct RuleKeyAssignment 32 | : RuleCandidate, RuleAction 33 | { 34 | // FIXME: drop this one, not that useful anymore 35 | 36 | public let key : K.Type 37 | public let value : Value.Type 38 | 39 | public init(_ key: K.Type, _ value: Value.Type) { 40 | assert(key != value, "assignment recursion!") 41 | // there seems to be no "where K != Value" in Swift generics? 42 | self.key = key 43 | self.value = value 44 | } 45 | 46 | public var candidateKeyType: ObjectIdentifier { 47 | return ObjectIdentifier(key) 48 | } 49 | public func isCandidateForKey(_ key: K.Type) 50 | -> Bool 51 | { 52 | return self.key == key 53 | } 54 | 55 | public func fireInContext(_ context: RuleContext) -> Any? { 56 | return context[value] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Assignments/RuleTypeIDAssignment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuleTypeIDAssignment.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 29.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * RuleTypeIDAssignment 11 | * 12 | * This class just returns its value if its fired. 13 | * 14 | * In a model it looks like: 15 | * 16 | * \user.role = 'Manager' => \.bannerColor = "red" 17 | * 18 | */ 19 | public struct RuleTypeIDAssignment: RuleCandidate, RuleAction { 20 | 21 | public let typeID : ObjectIdentifier 22 | public let constant : Value 23 | #if DEBUG 24 | let debugInfo : String 25 | #endif 26 | 27 | public init(_ typeID: ObjectIdentifier, _ constant: Value, 28 | debugInfo: String = "") 29 | { 30 | self.typeID = typeID 31 | self.constant = constant 32 | #if DEBUG 33 | self.debugInfo = debugInfo 34 | #endif 35 | } 36 | 37 | public var candidateKeyType: ObjectIdentifier { 38 | return typeID 39 | } 40 | 41 | public func isCandidateForKey(_ key: K.Type) 42 | -> Bool 43 | { 44 | return self.typeID == ObjectIdentifier(key) 45 | } 46 | 47 | public func fireInContext(_ context: RuleContext) -> Any? { 48 | return constant 49 | } 50 | } 51 | 52 | extension RuleTypeIDAssignment: CustomStringConvertible { 53 | 54 | public var description: String { 55 | #if DEBUG 56 | return "" 57 | #else 58 | return "" 59 | #endif 60 | } 61 | } 62 | 63 | public extension RuleTypeIDAssignment { 64 | 65 | init(_ key: K.Type, _ constant: K.Value) 66 | where Value == K.Value 67 | { 68 | self.init(ObjectIdentifier(key), constant) 69 | } 70 | 71 | /** 72 | * Careful, this only works properly for single-key keypathes. 73 | * 74 | * E.g. don't: \.person.name = "Hello" 75 | */ 76 | init(_ keyPath: KeyPath, _ constant: Value) { 77 | // FIXME: This is not quite what we want yet. The user could "write" to 78 | // an arbitrary keypath, e.g. \.person.name = "Donald". 79 | // I guess in theory it is possible to make that work if OIDs 80 | // of the keypathes are interned? 81 | // Then we could 'assign' them just like any other dynamic envkey? 82 | // Note sure yet. 83 | let typeID = RuleContext.typeIDForKeyPath(keyPath) 84 | #if DEBUG 85 | let debugInfo = 86 | "type=\(typeID.short) keyPath=\(keyPath) constant=\(constant)" 87 | #else 88 | let debugInfo = "" 89 | #endif 90 | self.init(typeID, constant, debugInfo: debugInfo) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Assignments/RuleTypeIDPathAssignment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuleTypeIDPathAssignment.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 29.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * RuleTypeIDPathAssignment 11 | * 12 | * This RuleAction object evaluates the action value as a lookup against the 13 | * _context_. Which then can trigger recursive rule evaluation (if the queried 14 | * key is itself a rule based value). 15 | * 16 | * In a model it looks like: 17 | * 18 | * \user.role == "Manager" => \.bannerColor <= \.defaultColor 19 | * 20 | * The `bannerColor <= defaultColor` represents the RuleKeyAssignment. 21 | * When executed, it will query the RuleContext for the 'defaultColor' and 22 | * will return that in fireInContext(). 23 | * 24 | * Note that this object does *not* assign to the \.bannerColor value. 25 | * It simply returns the value in fireInContext() for further processing at 26 | * upper layers. 27 | * 28 | * @see RuleAction 29 | * @see RuleAssignment 30 | */ 31 | public struct RuleTypeIDPathAssignment: RuleCandidate, RuleAction { 32 | 33 | public let typeID : ObjectIdentifier 34 | public let keyPath : Swift.KeyPath 35 | #if DEBUG 36 | let debugInfo : String 37 | #endif 38 | 39 | public init(_ typeID: ObjectIdentifier, 40 | _ keyPath: Swift.KeyPath, 41 | debugInfo: String = "") 42 | { 43 | self.typeID = typeID 44 | self.keyPath = keyPath 45 | #if DEBUG 46 | self.debugInfo = debugInfo 47 | #endif 48 | } 49 | 50 | public var candidateKeyType: ObjectIdentifier { 51 | return typeID 52 | } 53 | 54 | public func isCandidateForKey(_ key: K.Type) 55 | -> Bool 56 | { 57 | return self.typeID == ObjectIdentifier(key) 58 | } 59 | 60 | public func fireInContext(_ context: RuleContext) -> Any? { 61 | return context[keyPath: keyPath] 62 | } 63 | } 64 | 65 | extension RuleTypeIDPathAssignment: CustomStringConvertible { 66 | 67 | public var description: String { 68 | #if DEBUG 69 | return "" 70 | #else 71 | return "" 72 | #endif 73 | } 74 | } 75 | 76 | public extension RuleTypeIDPathAssignment { 77 | 78 | /** 79 | * Careful, this only works properly for single-key keypathes. 80 | * 81 | * E.g. don't: \.person.name = "Hello" 82 | */ 83 | init(_ keyPath : Swift.KeyPath, 84 | _ valuePath : Swift.KeyPath) 85 | { 86 | // FIXME: This is not quite what we want yet. The user could "write" to 87 | // an arbitrary keypath, e.g. \.person.name = "Donald". 88 | // I guess in theory it is possible to make that work if OIDs 89 | // of the keypathes are interned? 90 | // Then we could 'assign' them just like any other dynamic envkey? 91 | // Note sure yet. 92 | let typeID = RuleContext.typeIDForKeyPath(keyPath) 93 | #if DEBUG 94 | let debugInfo = 95 | "type=\(typeID.short) keyPath=\(keyPath) valuePath=\(valuePath)" 96 | #else 97 | let debugInfo = "" 98 | #endif 99 | self.init(typeID, valuePath, debugInfo: debugInfo) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Assignments/RuleValueAssignment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuleValueAssignment.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 23.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public struct RuleValueAssignment 10 | : RuleCandidate, RuleAction 11 | { 12 | // FIXME: drop this one, not that useful anymore 13 | 14 | public let key : K.Type 15 | public let constant : K.Value 16 | 17 | public init(_ key: K.Type, _ constant: K.Value) { 18 | self.key = key 19 | self.constant = constant 20 | } 21 | 22 | public var candidateKeyType: ObjectIdentifier { 23 | return ObjectIdentifier(key) 24 | } 25 | 26 | public func isCandidateForKey(_ key: K.Type) 27 | -> Bool 28 | { 29 | return self.key == key 30 | } 31 | 32 | public func fireInContext(_ context: RuleContext) -> Any? { 33 | return constant 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/DynamicEnvironment/DynamicEnvironmentKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicEnvironmentKey.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 20.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import protocol SwiftUI.EnvironmentKey 10 | 11 | /** 12 | * Environment keys which are dynamically evaluated against the RuleContext. 13 | */ 14 | public protocol DynamicEnvironmentKey: EnvironmentKey {} 15 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/DynamicEnvironment/DynamicEnvironmentPathes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicEnvironmentPathes.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 29.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import struct SwiftUI.EnvironmentValues 10 | 11 | /** 12 | * Use to share the environment keypathes between 13 | * `EnvironmentValues` and the `RuleContext`. 14 | * Both can then be accessed using `\.entity` like keypathes. 15 | * 16 | * DynamicEnvironmentPathes is only used to share the implementation, 17 | * it is not used as an own type. 18 | */ 19 | public protocol DynamicEnvironmentPathes { 20 | 21 | subscript(dynamic key: K.Type) -> K.Value { 22 | set get 23 | } 24 | 25 | } 26 | 27 | extension EnvironmentValues: DynamicEnvironmentPathes { 28 | 29 | public subscript(dynamic key: K.Type) -> K.Value { 30 | set { 31 | // Hm, we really want to write to the environment values? But we can't, 32 | // because we can't check whether the environment contains a value 33 | // w/o fatal-erroring? :-) 34 | // So we need to sideline our own storage? 35 | ruleContext[key] = newValue 36 | } 37 | get { return ruleContext[key] } 38 | } 39 | } 40 | 41 | extension RuleContext: DynamicEnvironmentPathes { 42 | 43 | public subscript(dynamic key: K.Type) -> K.Value { 44 | set { self[key] = newValue } 45 | get { return self[key] } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/DynamicEnvironment/DynamicEnvironmentValues.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicEnvironmentValues.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 28.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * A DynamicEnvironmentValues can yield values for `DynamicEnvironmentKeys`. 11 | * It is a protocol similar to the `EnvironmentValues` struct in SwiftUI. 12 | * 13 | * User code should not directly use the resolver and store functions, but 14 | * rather rely on the subscript and `optional` functions. 15 | */ 16 | public protocol DynamicEnvironmentValues { 17 | 18 | func resolveValueForTypeID(_ typeID: ObjectIdentifier) -> Any? 19 | 20 | mutating func storeValue(_ value: V, forTypeID typeID: ObjectIdentifier) 21 | 22 | func defaultValue(for key: K.Type) -> K.Value 23 | } 24 | 25 | public extension DynamicEnvironmentValues { // lookup using type 26 | 27 | /** 28 | * Returns a value for the `DynamicEnvironmentKey`, or the defaultValue of 29 | * the key if the `resolveValueForTypeID` returns no value. 30 | */ 31 | subscript(key: K.Type) -> K.Value { 32 | // Note a subscript w/o a name can be used on types w/o adding `.self`!! 33 | set { storeValue(newValue, forTypeID: ObjectIdentifier(key)) } 34 | get { return optional(key) ?? defaultValue(for: key) } 35 | } 36 | 37 | /** 38 | * Returns a value for the `DynamicEnvironmentKey` or `nil` in case the 39 | * `resolveValueForTypeID` returned no value (it could not be dynamically 40 | * generated). 41 | * Hence the result is optional. 42 | * 43 | * Note: Avoid having to check for optionality in user code. Rather use the 44 | * subscript, which falls back to the defaultValue of the environment 45 | * key. 46 | */ 47 | func optional(_ key: K.Type) -> K.Value? { 48 | let typeID = ObjectIdentifier(key) 49 | 50 | if debugPrints { 51 | print("optional lookup key:", key, "typeID:", typeID.short) 52 | } 53 | 54 | if let v = resolveValueForTypeID(typeID) { 55 | guard let typed = v as? K.Value else { 56 | print( // TBD: no generic logger? Want to avoid ZeeQL here 57 | "Could not map rule result to expected value:\n", 58 | " value: ", v, "\n", 59 | " expected:", K.Value.self 60 | ) 61 | return nil 62 | } 63 | return typed // TBD: cache? 64 | } 65 | 66 | return nil 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/DynamicEnvironment/README.md: -------------------------------------------------------------------------------- 1 |

SwiftUI Dynamic Environment Keys 2 | 4 |

5 | 6 | "Environment Keys" are keys which you can use like so in SwiftUI: 7 | 8 | ```swift 9 | public struct MyView: View { 10 | 11 | @Environment(\.database) private var database // retrieve a key 12 | 13 | var body: some View { 14 | BlaBlub() 15 | .environment(\.database, database) // set a key 16 | } 17 | } 18 | ``` 19 | 20 | They are scoped along the view hierarchy. 21 | 22 | `DynamicEnvironmentKeys` are the similar, but can resolve to different values on 23 | demand. 24 | 25 | For example a rule system could evaluate them like so: 26 | 27 | day < 28 => color = green 28 | day >= 28 => color = red 29 | *true* => color = white 30 | 31 | But they don't have the be backed by a rule system. 32 | `DynamicEnvironmentKey` and `DynamicEnvironmentValues` 33 | just form an API to back such keys. 34 | 35 | 36 | ### Implementation 37 | 38 | While the `DynamicEnvironmentKey` forms a statically typed interface, 39 | the actual dynamic workings are defined in terms of the `ObjectIdentifier`s 40 | of those types. 41 | For various reasons :-) 42 | 43 | 44 | ### Allow arbitrary Environment Keys 45 | 46 | It might make sense to allow lookup of any environment key, not just dynamic ones. 47 | TBD. 48 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Operators/AssignmentOperators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssignmentOperators.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 01.09.19. 6 | // 7 | 8 | import protocol SwiftUI.View 9 | import struct SwiftUI.AnyView 10 | 11 | // \.title <= "hello" 12 | public func <= (lhs: Swift.KeyPath, rhs: Value) 13 | -> RuleTypeIDAssignment 14 | { 15 | RuleTypeIDAssignment(lhs, rhs) 16 | } 17 | 18 | // \.title <= "hello" 19 | public func <= (lhs: Swift.KeyPath, 20 | rhs: Swift.KeyPath) 21 | -> RuleTypeIDPathAssignment 22 | { 23 | RuleTypeIDPathAssignment(lhs, rhs) 24 | } 25 | 26 | // \.view <= MyView() 27 | public func <= (lhs: Swift.KeyPath, rhs: V) 28 | -> RuleTypeIDAssignment 29 | { 30 | RuleTypeIDAssignment(lhs, AnyView(rhs)) 31 | } 32 | public func <= (lhs: Swift.KeyPath, rhs: AnyView) 33 | -> RuleTypeIDAssignment 34 | { 35 | RuleTypeIDAssignment(lhs, rhs) 36 | } 37 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Operators/CompoundPredicateOperators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompoundPredicateOperators.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 01.09.19. 6 | // 7 | 8 | // e.g. !predicate 9 | public prefix func !(_ base: P) -> RuleNotPredicate

{ 10 | RuleNotPredicate(predicate: base) 11 | } 12 | 13 | // e.g. \.state == done && \.color == yellow 14 | public 15 | func &&(lhs: LHS, rhs: RHS) -> RuleAndPredicate2 16 | where LHS: RulePredicate, RHS: RulePredicate 17 | { 18 | RuleAndPredicate2(lhs, rhs) 19 | } 20 | // e.g. \.state == done || \.color == yellow 21 | public 22 | func ||(lhs: LHS, rhs: RHS) -> RuleOrPredicate2 23 | where LHS: RulePredicate, RHS: RulePredicate 24 | { 25 | RuleOrPredicate2(lhs, rhs) 26 | } 27 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Operators/KeyPathPredicateOperators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PredicateOperators.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 01.09.19. 6 | // 7 | 8 | // MARK: - KeyValue 9 | 10 | // e.g. \.person.name == "Donald" 11 | public 12 | func ==(lhs: Swift.KeyPath, rhs: Value) 13 | -> some RulePredicate where Value : Equatable 14 | { 15 | RuleKeyPathPredicate(keyPath: lhs, value: rhs) 16 | } 17 | 18 | // e.g. \.person.name != "Donald" 19 | public 20 | func !=(lhs: Swift.KeyPath, rhs: Value) 21 | -> some RulePredicate where Value : Equatable 22 | { 23 | RuleNotPredicate(predicate: 24 | RuleKeyPathPredicate(keyPath: lhs, value: rhs)) 25 | } 26 | 27 | // e.g. \.person.age < 45 28 | public 29 | func < (lhs: Swift.KeyPath, rhs: Value) 30 | -> some RulePredicate where Value : Comparable 31 | { 32 | RuleKeyPathPredicate(keyPath: lhs, operation: .lessThan, value: rhs) 33 | } 34 | public 35 | func <= (lhs: Swift.KeyPath, rhs: Value) 36 | -> some RulePredicate where Value : Comparable 37 | { 38 | RuleKeyPathPredicate(keyPath: lhs, operation: .lessThanOrEqual, 39 | value: rhs) 40 | } 41 | 42 | // e.g. \.person.age > 45 43 | public 44 | func > (lhs: Swift.KeyPath, rhs: Value) 45 | -> some RulePredicate where Value : Comparable 46 | { 47 | RuleKeyPathPredicate(keyPath: lhs, operation: .greaterThan, value: rhs) 48 | } 49 | public 50 | func >= (lhs: Swift.KeyPath, rhs: Value) 51 | -> some RulePredicate where Value : Comparable 52 | { 53 | RuleKeyPathPredicate(keyPath: lhs, operation: .greaterThanOrEqual, 54 | value: rhs) 55 | } 56 | 57 | // e.g. \.person === manager 58 | public func ===(lhs: Swift.KeyPath, rhs: Value) 59 | -> some RulePredicate where Value : AnyObject 60 | { 61 | RuleKeyPathPredicate() { ruleContext in 62 | ruleContext[keyPath: lhs] === rhs 63 | } 64 | } 65 | // e.g. \.person !== manager 66 | public func !==(lhs: Swift.KeyPath, rhs: Value) 67 | -> some RulePredicate where Value : AnyObject 68 | { 69 | RuleKeyPathPredicate() { ruleContext in 70 | ruleContext[keyPath: lhs] !== rhs 71 | } 72 | } 73 | 74 | 75 | // MARK: - KeyKey 76 | 77 | // e.g. \.person.name == \.manager.name 78 | public 79 | func ==(lhs: Swift.KeyPath, 80 | rhs: Swift.KeyPath) 81 | -> some RulePredicate where Value : Equatable 82 | { 83 | RuleKeyPathPredicate(lhs, rhs) 84 | } 85 | 86 | // e.g. \.person.name != \.manager.name 87 | public 88 | func !=(lhs: Swift.KeyPath, 89 | rhs: Swift.KeyPath) 90 | -> some RulePredicate where Value : Equatable 91 | { 92 | RuleNotPredicate(predicate: RuleKeyPathPredicate(lhs, rhs)) 93 | } 94 | 95 | // e.g. \.person.age < \.manager.age 96 | public 97 | func < (lhs: Swift.KeyPath, 98 | rhs: Swift.KeyPath) 99 | -> some RulePredicate where Value : Comparable 100 | { 101 | RuleKeyPathPredicate(lhs, operation: .lessThan, rhs) 102 | } 103 | // e.g. \.person.age <= \.manager.age 104 | public 105 | func <= (lhs: Swift.KeyPath, 106 | rhs: Swift.KeyPath) 107 | -> some RulePredicate where Value : Comparable 108 | { 109 | RuleKeyPathPredicate(lhs, operation: .lessThanOrEqual, rhs) 110 | } 111 | 112 | // e.g. \.person.age > \.manager.age 113 | public 114 | func > (lhs: Swift.KeyPath, 115 | rhs: Swift.KeyPath) 116 | -> some RulePredicate where Value : Comparable 117 | { 118 | RuleKeyPathPredicate(lhs, operation: .greaterThan, rhs) 119 | } 120 | // e.g. \.person.age >= \.manager.age 121 | public 122 | func >= (lhs: Swift.KeyPath, 123 | rhs: Swift.KeyPath) 124 | -> some RulePredicate where Value : Comparable 125 | { 126 | RuleKeyPathPredicate(lhs, operation: .greaterThanOrEqual, rhs) 127 | } 128 | 129 | // e.g. \.person === manager 130 | public func ===(lhs: Swift.KeyPath, 131 | rhs: Swift.KeyPath) 132 | -> some RulePredicate where Value : AnyObject 133 | { 134 | RuleKeyPathPredicate() { ruleContext in 135 | ruleContext[keyPath: lhs] === ruleContext[keyPath: rhs] 136 | } 137 | } 138 | // e.g. \.person !== manager 139 | public func !==(lhs: Swift.KeyPath, 140 | rhs: Swift.KeyPath) 141 | -> some RulePredicate where Value : AnyObject 142 | { 143 | RuleKeyPathPredicate() { ruleContext in 144 | ruleContext[keyPath: lhs] !== ruleContext[keyPath: rhs] 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Operators/README.md: -------------------------------------------------------------------------------- 1 | # SwiftUI Rules Operators 2 | 3 | Not a huge fan of operator overloading, but well :-) 4 | 5 | A set of operators to build rule predicates, assignments and rules. 6 | 7 | Rule Predicate samples: 8 | ```swift 9 | \.count < 10 10 | \.person.name == "Duck" 11 | ``` 12 | 13 | Rule Assignment samples: 14 | ```swift 15 | \.title <= "1337" 16 | \.title <= \.defaultTitle 17 | ``` 18 | 19 | Rule samples: 20 | ```swift 21 | \.count > 5 => \.color <= .red 22 | \.count > 5 && \.person.name == "Duck" => \.color <= \.defaultColor 23 | ``` 24 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Operators/RuleOperators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuleOperators.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 29.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | infix operator => : AssignmentPrecedence 10 | 11 | /** 12 | * Operator to combine a predicate with an action to form the final rule. 13 | * 14 | * Example: 15 | * 16 | * \.person.name != "Donald" => \.title <= "hello" 17 | * 18 | */ 19 | public func =>(lhs: RulePredicate, rhs: RuleCandidate & RuleAction) -> Rule { 20 | Rule(when: lhs, do: rhs) 21 | } 22 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Predicates/README.md: -------------------------------------------------------------------------------- 1 |

SwiftUI Rule Predicates 2 | 4 |

5 | 6 | The predicate defines whether a rule is active for a given context. For 7 | example if a rule has such a qualifier: 8 | 9 | ```swift 10 | \.user.role == "Manager" 11 | ``` 12 | 13 | It will only be considered for evaluation if the current user has a Manager 14 | role. 15 | 16 | The key method which all rule predicates implement is: 17 | 18 | ```swift 19 | func evaluate(in ruleContext: RuleContext) -> Bool 20 | ``` 21 | 22 | It just returns true or false depending on whether the predicate matches for the 23 | given `ruleContext`. 24 | 25 | ### Predicate Complexity 26 | 27 | Another `RulePredicate` property is `rulePredicateComplexity`, which defaults to 1. 28 | 29 | The complexity is used to sort predicates based on how many components 30 | or relevance a predicate has. It is used to disambiguate in situations 31 | like: 32 | 33 | ```swift 34 | \.person.name == "Duck" => \.title <= "Donald" 35 | \.task == "show" && \.person.name == "Persons" => \.title <= "Brummel" 36 | ``` 37 | 38 | In this case the second rule is checked first, because the predicate 39 | has more components (and hence assumed significance). 40 | 41 | ### KeyPath Predicate 42 | 43 | This is the most common predicate, it takes a keypath and a matching value: 44 | 45 | ```swift 46 | \.person.name == "Duck" 47 | ``` 48 | 49 | When it is evaluated, it resolves the keypath against the ruleContext and returns 50 | whether the value matches the constant passed into the predicate. 51 | 52 | ### Compound Predicates 53 | 54 | The usual AND, OR and NOT predicates. Can be constructed using the 55 | `&&`, `||` and `!` operators. 56 | 57 | ```swift 58 | \.person.name == "Duck" && !(\.person.category == "VIP") 59 | ``` 60 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Predicates/RuleBoolPredicate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuleBoolPredicate.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 29.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public struct RuleBoolPredicate: RulePredicate, Equatable { 10 | 11 | public static let yes = RuleBoolPredicate(value: true) 12 | public static let no = RuleBoolPredicate(value: false) 13 | 14 | private let value : Bool 15 | 16 | public func evaluate(in ruleContext: RuleContext) -> Bool { 17 | return value 18 | } 19 | 20 | public var rulePredicateComplexity: Int { 21 | // This means that a boolean predicate has a lower predicate than 22 | // any other predicate, even simple ones. 23 | return 0 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Predicates/RuleClosurePredicate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuleClosurePredicate.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 29.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public struct RuleClosurePredicate: RulePredicate { 10 | 11 | private let predicate : ( RuleContext ) -> Bool 12 | 13 | public init(predicate: @escaping ( RuleContext ) -> Bool) { 14 | self.predicate = predicate 15 | } 16 | 17 | public func evaluate(in ruleContext: RuleContext) -> Bool { 18 | return predicate(ruleContext) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Predicates/RuleCompoundPredicate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuleCompoundPredicate.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 29.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | // Provide a default set of predicates. 10 | 11 | public protocol RuleCompoundPredicate: RulePredicate { 12 | 13 | var predicates : [ RulePredicate ] { get } 14 | } 15 | 16 | public extension RuleCompoundPredicate { 17 | var rulePredicateComplexity: Int { 18 | predicates.reduce(0) { $0 + $1.rulePredicateComplexity } 19 | } 20 | } 21 | 22 | public struct RuleAndPredicate2: RuleCompoundPredicate 23 | where P1: RulePredicate, P2: RulePredicate 24 | { 25 | public let p1: P1 26 | public let p2: P2 27 | 28 | public var predicates : [ RulePredicate ] { [ p1, p2 ] } 29 | 30 | public init(_ p1: P1, _ p2: P2) { 31 | self.p1 = p1 32 | self.p2 = p2 33 | } 34 | public var rulePredicateComplexity: Int { 35 | p1.rulePredicateComplexity + p2.rulePredicateComplexity 36 | } 37 | 38 | public func evaluate(in ruleContext: RuleContext) -> Bool { 39 | return p1.evaluate(in: ruleContext) && p2.evaluate(in: ruleContext) 40 | } 41 | 42 | } 43 | public struct RuleOrPredicate2: RuleCompoundPredicate 44 | where P1: RulePredicate, P2: RulePredicate 45 | { 46 | public let p1: P1 47 | public let p2: P2 48 | 49 | public var predicates : [ RulePredicate ] { [ p1, p2 ] } 50 | 51 | public init(_ p1: P1, _ p2: P2) { 52 | self.p1 = p1 53 | self.p2 = p2 54 | } 55 | public var rulePredicateComplexity: Int { 56 | p1.rulePredicateComplexity + p2.rulePredicateComplexity 57 | } 58 | 59 | public func evaluate(in ruleContext: RuleContext) -> Bool { 60 | return p1.evaluate(in: ruleContext) || p2.evaluate(in: ruleContext) 61 | } 62 | 63 | } 64 | 65 | public struct RuleAndPredicate: RuleCompoundPredicate { 66 | 67 | public let predicates : [ RulePredicate ] 68 | 69 | public init(predicates: [ RulePredicate ]) { 70 | self.predicates = predicates 71 | } 72 | 73 | public func evaluate(in ruleContext: RuleContext) -> Bool { 74 | for predicate in predicates { 75 | if !predicate.evaluate(in: ruleContext) { return false } 76 | } 77 | return true 78 | } 79 | 80 | } 81 | public struct RuleOrPredicate: RuleCompoundPredicate { 82 | 83 | public let predicates : [ RulePredicate ] 84 | 85 | public init(predicates: [ RulePredicate ]) { 86 | self.predicates = predicates 87 | } 88 | 89 | public func evaluate(in ruleContext: RuleContext) -> Bool { 90 | for predicate in predicates { 91 | if predicate.evaluate(in: ruleContext) { return true } 92 | } 93 | return false 94 | } 95 | } 96 | 97 | public struct RuleNotPredicate: RulePredicate { 98 | 99 | public let predicate : P 100 | 101 | public init(predicate: P) { 102 | self.predicate = predicate 103 | } 104 | 105 | public func evaluate(in ruleContext: RuleContext) -> Bool { 106 | return !predicate.evaluate(in: ruleContext) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Predicates/RuleKeyPathPredicate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuleKeyPathPredicate.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 29.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public struct RuleKeyPathPredicate: RulePredicate { 10 | // This is like a ClosurePredicate, but it preserves the static type. 11 | // Which has to be passed in explicitly! Can't add further constraints to 12 | // the init, like init() where Value: Equatable 13 | 14 | private let predicate : ( RuleContext ) -> Bool 15 | #if DEBUG 16 | private let debugInfo : String 17 | #endif 18 | 19 | public init(debugInfo: String = "", 20 | predicate: @escaping ( RuleContext ) -> Bool) 21 | { 22 | #if DEBUG 23 | self.debugInfo = debugInfo 24 | #endif 25 | self.predicate = predicate 26 | } 27 | 28 | public func evaluate(in ruleContext: RuleContext) -> Bool { 29 | return predicate(ruleContext) 30 | } 31 | } 32 | 33 | extension RuleKeyPathPredicate: CustomStringConvertible { 34 | 35 | public var description: String { 36 | #if DEBUG 37 | return "" 38 | #else 39 | return "" 40 | #endif 41 | } 42 | } 43 | 44 | public enum RuleComparisonOperation { 45 | 46 | case equal 47 | case notEqual 48 | case lessThan 49 | case greaterThan 50 | case lessThanOrEqual 51 | case greaterThanOrEqual 52 | 53 | public func compare(_ lhs: V, _ rhs: V) -> Bool { 54 | switch self { 55 | case .equal: return lhs == rhs 56 | case .notEqual: return lhs != rhs 57 | case .lessThan: return lhs < rhs 58 | case .greaterThan: return lhs > rhs 59 | case .lessThanOrEqual: return lhs <= rhs 60 | case .greaterThanOrEqual: return lhs >= rhs 61 | } 62 | } 63 | } 64 | 65 | 66 | public extension RuleKeyPathPredicate { 67 | 68 | init(keyPath: Swift.KeyPath, 69 | value: Value) where Value: Equatable 70 | { 71 | #if DEBUG 72 | let debugInfo = "keyPath=\(keyPath) value=\(value)" 73 | #else 74 | let debugInfo = "" 75 | #endif 76 | 77 | self.init(debugInfo: debugInfo) { ruleContext in 78 | let other = ruleContext[keyPath: keyPath] 79 | return value == other 80 | } 81 | } 82 | 83 | init(keyPath: Swift.KeyPath, 84 | operation: RuleComparisonOperation = .equal, 85 | value: Value) where Value: Comparable 86 | { 87 | self.init() { ruleContext in 88 | return operation.compare(ruleContext[keyPath: keyPath], value) 89 | } 90 | } 91 | } 92 | 93 | public extension RuleKeyPathPredicate { 94 | init(_ lhs: Swift.KeyPath, 95 | _ rhs: Swift.KeyPath) where Value: Equatable 96 | { 97 | #if DEBUG 98 | let debugInfo = "lhs=\(lhs) rhs=\(rhs)" 99 | #else 100 | let debugInfo = "" 101 | #endif 102 | 103 | self.init(debugInfo: debugInfo) { ruleContext in 104 | let lhsValue = ruleContext[keyPath: lhs] 105 | let rhsValue = ruleContext[keyPath: rhs] 106 | return lhsValue == rhsValue 107 | } 108 | } 109 | 110 | init(_ lhs: Swift.KeyPath, 111 | operation: RuleComparisonOperation = .equal, 112 | _ rhs: Swift.KeyPath) where Value: Comparable 113 | { 114 | self.init() { ruleContext in 115 | let lhsValue = ruleContext[keyPath: lhs] 116 | let rhsValue = ruleContext[keyPath: rhs] 117 | return operation.compare(lhsValue, rhsValue) 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Predicates/RulePredicate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RulePredicate.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 28.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public protocol RulePredicate { 10 | 11 | /** 12 | * Returns true if a predicate matches in the rule context. 13 | * 14 | * For example: 15 | * 16 | * entity.name = 'Persons' 17 | * 18 | */ 19 | func evaluate(in ruleContext: RuleContext) -> Bool 20 | 21 | /** 22 | * The complexity is used to sort predicates based on how many components 23 | * or relevance a predicate has. It is used to disambiguate in situations 24 | * like: 25 | * 26 | * \.person.name == "Duck" => \.title <= "Donald" 27 | * \.task == "show" && \.person.name == "Persons" => \.title <= "Brummel" 28 | * 29 | * In this case the second rule is checked first, because the predicate 30 | * has more components (and hence assumed significance). 31 | */ 32 | var rulePredicateComplexity : Int { get } 33 | 34 | } 35 | 36 | public extension RulePredicate { 37 | var rulePredicateComplexity : Int { 38 | return 1 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Rule.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Rule.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 23.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * A Rule consists of three major components: 11 | * - a predicate, also known as the "left hand side" or lhs 12 | * - an action, also known as the "right hand side" or rhs 13 | * - a priority 14 | * 15 | * The predicate defines whether a rule is active for a given context. For 16 | * example if a rule has such a qualifier: 17 | * 18 | * \.user.role == "Manager" 19 | * 20 | * It will only be considered for evaluation if the current user has a Manager 21 | * role. 22 | * 23 | * The RuleAction defines what gets executed when a rule was selected for 24 | * evaluation. If the rule was the best condidate for a given query, the 25 | * framework will call the `fireInContext()` method on the action. 26 | * Most often the RuleAction will be an object of the RuleAssignment class, 27 | * sometimes a RuleKeyAssignment. 28 | * Sample: 29 | * 30 | * \.user.role == "Manager" => bannerColor <= "red" 31 | * 32 | * Note that the name 'Action' does not imply that the object somehow modifies 33 | * state in the context, it usually does not have any sideeffects. Instead the 34 | * action just *returns* the value for a requested key by performing some 35 | * evaluation/calculation. 36 | * 37 | * Finally the 'priority' is used to select a rule when there are multiple 38 | * matching rules. In models you usually use constants like 'fallback' or 39 | * 'high'. 40 | * 41 | * (\.firstPageName <= "Main' ).priority(.fallback) 42 | * (\.firstPageName <= "MyMain").priority(.default) 43 | * 44 | * The 'fallback' priority is most often in frameworks to specify 45 | * defaults for keys which can then be overridden in user provided models. 46 | */ 47 | public final class Rule { 48 | // Note: immutable class because it is passed around a bit (too lazy for 🐄) 49 | 50 | public struct Priority: Comparable, ExpressibleByIntegerLiteral { 51 | 52 | public let rawValue : Int16 53 | @inlinable public init(_ rawValue: Int16) { self.rawValue = rawValue } 54 | @inlinable public init(integerLiteral value: Int16) { self.init(value) } 55 | 56 | public static let important = Priority(1000) 57 | public static let veryHigh = Priority(200) 58 | public static let high = Priority(150) 59 | public static let normal = Priority(100) 60 | public static let low = Priority(50) 61 | public static let veryLow = Priority(5) 62 | public static let fallback = Priority(0) 63 | 64 | @inlinable 65 | public static func < (lhs: Priority, rhs: Priority) -> Bool { 66 | return lhs.rawValue < rhs.rawValue 67 | } 68 | } 69 | 70 | public let predicate : RulePredicate 71 | public let action : RuleCandidate & RuleAction 72 | public let priority : Priority 73 | 74 | public init(when predicate : RulePredicate, 75 | do action : RuleCandidate & RuleAction, 76 | at priority : Priority = .normal) 77 | { 78 | self.predicate = predicate 79 | self.action = action 80 | self.priority = priority 81 | } 82 | 83 | /** 84 | * Return a new rule at a different priority. 85 | * 86 | * (\.isNew == true => \.title <= "Hello").priority(.high) 87 | */ 88 | public func priority(_ priority: Priority) -> Rule { 89 | return Rule(when: predicate, do: action, at: priority) 90 | } 91 | 92 | func hasHigherPriority(than other: Rule) -> Bool { 93 | if self === other { return false } 94 | 95 | if priority != other.priority { return priority > other.priority } 96 | return predicate.rulePredicateComplexity 97 | > other.predicate.rulePredicateComplexity 98 | } 99 | } 100 | 101 | extension Rule: RuleCandidate { 102 | 103 | public var candidateKeyType: ObjectIdentifier { 104 | return action.candidateKeyType 105 | } 106 | public func isCandidateForKey(_ key: K.Type) -> Bool { 107 | return action.isCandidateForKey(key) 108 | } 109 | 110 | } 111 | 112 | extension Rule: RuleAction { 113 | 114 | public func fireInContext(_ context: RuleContext) -> Any? { 115 | return action.fireInContext(context) 116 | } 117 | 118 | } 119 | 120 | extension Rule: CustomStringConvertible { 121 | 122 | public var description: String { 123 | var ms = " Rule { 167 | return Rule(when: RuleBoolPredicate.yes, do: self, at: priority) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/RuleContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuleContext.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 19.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * The `RuleContext` holds the state of the rule system for a given environment. 11 | * 12 | * It is itself an environment key and can be accessed using 13 | * 14 | * @Environment(\.ruleContext) var ruleContext 15 | * 16 | * Keys in the context can be accessed as regular properties, for example: 17 | * 18 | * ruleContext.color 19 | * 20 | * Or as usual as regular environment keys: 21 | * 22 | * @Environment(\.color) var color 23 | * 24 | * The context has a state dictionary mapping `DynamicEnvironmentKeys` to their 25 | * values. Which means you can explicitly fill in values: 26 | * 27 | * var body: some View { 28 | * Text() 29 | * .environment(\.color, .red) 30 | * } 31 | * 32 | * If a RuleContext has state for a key, it always has preference over the 33 | * rules. 34 | * Its true power stems however from the fact that keys can be looked up using 35 | * the associated `RuleModel`: 36 | * 37 | * ruleModel 38 | * .add(\todo.status == "pending" => \.color <= .yellow) 39 | * .add(\todo.status == "overdue" => \.color <= .red) 40 | * .add(\todo.status == "done" => \.color <= .gray) 41 | * 42 | * That is transparent to the View, it can still access the dynamic key using: 43 | * 44 | * @Environment(\.color) var color 45 | * 46 | */ 47 | public struct RuleContext: DynamicEnvironmentValues { 48 | 49 | public let ruleModel : RuleModel 50 | 51 | private var state = [ ObjectIdentifier : Any ]() 52 | // We'd like to avoid this and just use `EnvironmentValues`, but we 53 | // can't. Because `EnvironmentValues` has nothing to check for the 54 | // existance of a key. 55 | 56 | public init(ruleModel: RuleModel) { 57 | self.ruleModel = ruleModel 58 | } 59 | 60 | @inline(__always) 61 | private func clearCache() {} // TBD 62 | 63 | // lookup using typeID 64 | 65 | public func resolveValueForTypeID(_ typeID: ObjectIdentifier) -> Any? { 66 | if crazyTypeQueryHack(typeID) { return nil } 67 | 68 | if let value = state[typeID] { 69 | if debugPrints { print("Resolved from state:", typeID.short, "=>", value) } 70 | return value 71 | } 72 | if let value = ruleModel.resolveValueForTypeID(typeID, in: self) { 73 | if debugPrints { 74 | print("Resolved using model:", typeID.short, "=>", value) 75 | } 76 | // The problem w/ caching is that our structs should stay immutable. 77 | // We could maybe use an object for the cache and be careful w/ CoW. 78 | return value // TBD: cache? 79 | } 80 | return nil 81 | } 82 | 83 | public mutating func storeValue(_ value: V, 84 | forTypeID typeID: ObjectIdentifier) 85 | { 86 | if crazyTypeQueryHack(typeID) { return } 87 | 88 | clearCache() 89 | if debugPrints { print("Push to state:", typeID.short, "=", value) } 90 | state[typeID] = value 91 | } 92 | 93 | public func defaultValue(for key: K.Type) -> K.Value 94 | { 95 | return K.defaultValue 96 | } 97 | } 98 | 99 | extension RuleContext: CustomStringConvertible { 100 | 101 | public var description: String { 102 | var ms = " Bool { 160 | guard queryTypeMode else { return false } 161 | if debugPrints { print(" crazyTypeQueryHack:", typeID.short) } 162 | assert(lastTypeQuery == nil) 163 | lastTypeQuery = typeID 164 | return true 165 | } 166 | 167 | static 168 | func typeIDForKeyPath(_ keyPath: Swift.KeyPath) 169 | -> ObjectIdentifier 170 | { 171 | // What a dirty hack :-> 172 | if debugPrints { print("TypeQuery lookup:", keyPath, Value.self) } 173 | 174 | // sigh, it can be recursive 175 | let storeQueryTypeMode = queryTypeMode 176 | let storeQueryType = lastTypeQuery 177 | queryTypeMode = true 178 | lastTypeQuery = nil 179 | defer { 180 | queryTypeMode = storeQueryTypeMode 181 | lastTypeQuery = storeQueryType 182 | } 183 | 184 | let tempContext = RuleContext(ruleModel: RuleModel(nil)) 185 | _ = tempContext[keyPath: keyPath] 186 | 187 | guard let typeID = lastTypeQuery else { 188 | fatalError("could not reflect type of keypath: \(keyPath)") 189 | } 190 | 191 | if debugPrints { print("=>", typeID.short) } 192 | 193 | return typeID 194 | } 195 | 196 | } 197 | 198 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/RuleModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuleModel.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 19.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * A rule model is just an ordered and keyed set of `Rule` values. 11 | * 12 | * To add rules the SwiftUIRule operators are usually used, for example: 13 | * 14 | * ruleModel 15 | * .add(\todo.status == "pending" => \.color <= .yellow) 16 | * .add(\todo.status == "overdue" => \.color <= .red) 17 | * .add(\todo.status == "done" => \.color <= .gray) 18 | * 19 | * You can also use to setup the rules: 20 | * 21 | * RuleModel( 22 | * \todo.status == "pending" => \.color <= .yellow, 23 | * \todo.status == "overdue" => \.color <= .red 24 | * ) 25 | * 26 | * Or use the literal convertible: 27 | * 28 | * let ruleModel : RuleModel = [ 29 | * \todo.status == "pending" => \.color <= .yellow, 30 | * \todo.status == "overdue" => \.color <= .red 31 | * ] 32 | * 33 | * Though rules can also be constructed manually and then added using `addRule`. 34 | */ 35 | public final class RuleModel { 36 | 37 | let fallbackModel: RuleModel? // kinda the model group? 38 | 39 | public init(_ fallbackModel: RuleModel? = nil, _ rules: RuleLiteral...) { 40 | self.fallbackModel = fallbackModel 41 | rules.forEach { $0.addToRuleModel(self) } 42 | if !rules.isEmpty { sortRules() } 43 | } 44 | 45 | /** 46 | * Add a fallback model to the RuleModel. The rules in the fallback model 47 | * will be used when none of the own rules matches (but before resorting 48 | * to defaults!) 49 | */ 50 | public func fallback(_ fallbackModel: RuleModel) -> RuleModel { 51 | let newModel : RuleModel 52 | if let oldFallback = self.fallbackModel { // nest fallbacks 53 | newModel = RuleModel(fallbackModel.fallback(oldFallback)) 54 | } 55 | else { 56 | newModel = RuleModel(fallbackModel) 57 | } 58 | newModel.oidToRules = self.oidToRules 59 | newModel.sortRequired = self.sortRequired 60 | return newModel 61 | } 62 | 63 | private var oidToRules = [ ObjectIdentifier : [ Rule ] ]() 64 | 65 | private var sortRequired = false 66 | 67 | private func sortRules() { 68 | for ( typeID, rules ) in oidToRules { 69 | if rules.isEmpty { oidToRules.removeValue(forKey: typeID); continue } 70 | oidToRules[typeID] = rules.sorted { $0.hasHigherPriority(than: $1) } 71 | } 72 | sortRequired = false 73 | } 74 | 75 | @discardableResult 76 | public func addRule(_ rule: Rule) -> Self { 77 | let typeID = rule.candidateKeyType 78 | if oidToRules[typeID] == nil { 79 | oidToRules[typeID] = [ rule ] 80 | return self 81 | } 82 | oidToRules[typeID]!.append(rule) 83 | sortRequired = true 84 | return self 85 | } 86 | 87 | func resolveValueForTypeID(_ typeID: ObjectIdentifier, 88 | in context: RuleContext) -> Any? 89 | { 90 | if sortRequired { sortRules() } 91 | 92 | // lookup candidates 93 | let rules = oidToRules[typeID] ?? [] 94 | 95 | if debugPrints { print("RULES: lookup:", typeID.short) } 96 | 97 | for rule in rules { 98 | if rule.predicate.evaluate(in: context) { 99 | if debugPrints { print("RULES: match:", rule) } 100 | return rule.fireInContext(context) 101 | } 102 | else { 103 | if debugPrints { print("RULES: no-match:", rule) } 104 | } 105 | } 106 | 107 | if let fallbackModel = fallbackModel { 108 | if debugPrints { print("RULES: test fallback:", typeID.short) } 109 | return fallbackModel.resolveValueForTypeID(typeID, in: context) 110 | } 111 | if debugPrints { print("RULES: non-matched:", typeID.short) } 112 | return nil 113 | } 114 | 115 | } 116 | 117 | extension RuleModel: CustomStringConvertible { 118 | 119 | public var description: String { 120 | if oidToRules.isEmpty { return "" } 121 | var ms = "(_ key: K.Type) 147 | -> Bool 148 | { 149 | oidToRules[ObjectIdentifier(key)] != nil 150 | } 151 | } 152 | #endif 153 | 154 | 155 | // MARK: - Convenience adders 156 | 157 | public extension RuleModel { 158 | 159 | @discardableResult 160 | func add(_ rule: Rule) -> Self { 161 | return addRule(rule) 162 | } 163 | 164 | @discardableResult 165 | func add(_ action: RuleCandidate & RuleAction) -> Self { 166 | return addRule(Rule(when: RuleBoolPredicate.yes, do: action)) 167 | } 168 | 169 | @discardableResult 170 | func add(_ predicate: @escaping ( RuleContext ) -> Bool, 171 | action: RuleCandidate & RuleAction) -> Self 172 | { 173 | return addRule(Rule(when: RuleClosurePredicate(predicate: predicate), 174 | do: action)) 175 | } 176 | } 177 | 178 | 179 | // MARK: - Literals 180 | 181 | /** 182 | * Literal convertible: 183 | * 184 | * let ruleModel : RuleModel = [ 185 | * \todo.status == "pending" => \.color <= .yellow, 186 | * \todo.status == "overdue" => \.color <= .red 187 | * ] 188 | * 189 | */ 190 | extension RuleModel : ExpressibleByArrayLiteral { 191 | public typealias ArrayLiteralElement = RuleLiteral 192 | 193 | public convenience init(arrayLiteral rules: RuleLiteral...) { 194 | self.init(nil) 195 | rules.forEach { $0.addToRuleModel(self) } 196 | sortRules() 197 | } 198 | } 199 | public protocol RuleLiteral { 200 | func addToRuleModel(_ model: RuleModel) 201 | } 202 | extension Rule: RuleLiteral { 203 | public func addToRuleModel(_ model: RuleModel) { 204 | model.addRule(self) 205 | } 206 | } 207 | extension RuleAction where Self: RuleCandidate { 208 | public func addToRuleModel(_ model: RuleModel) { 209 | model.addRule(Rule(when: RuleBoolPredicate.yes, do: self)) 210 | } 211 | } 212 | extension RuleTypeIDAssignment : RuleLiteral {} 213 | extension RuleTypeIDPathAssignment : RuleLiteral {} 214 | 215 | extension RuleModel: RuleLiteral { 216 | public func addToRuleModel(_ model: RuleModel) { 217 | for rules in oidToRules.values { 218 | rules.forEach { model.addRule($0) } 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /Sources/SwiftUIRules/Support/RuleDebug.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RuleDebug.swift 3 | // SwiftUIRules 4 | // 5 | // Created by Helge Heß on 29.08.19. 6 | // Copyright © 2019 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import class Foundation.ProcessInfo 10 | 11 | internal let debugPrints : Bool = { 12 | guard let v = ProcessInfo.processInfo.environment["RULES_LOG"]?.lowercased() 13 | else { return false } 14 | return v.contains("debug") 15 | }() 16 | 17 | extension ObjectIdentifier { 18 | 19 | var short: String { 20 | let s = String(describing: self) 21 | let match = "ObjectIdentifier(0x0000000" 22 | if s.hasPrefix(match) { 23 | return "0x" + String(s.dropFirst(match.count).dropLast()) 24 | } 25 | return s 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /SwiftUIRules.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXAggregateTarget section */ 10 | E8A02CFA2319495800C1D879 /* SwiftUIRules-All */ = { 11 | isa = PBXAggregateTarget; 12 | buildConfigurationList = E8A02CFB2319495800C1D879 /* Build configuration list for PBXAggregateTarget "SwiftUIRules-All" */; 13 | buildPhases = ( 14 | ); 15 | dependencies = ( 16 | E8A02CFF2319496100C1D879 /* PBXTargetDependency */, 17 | E8A02D012319496100C1D879 /* PBXTargetDependency */, 18 | E8A02D032319496100C1D879 /* PBXTargetDependency */, 19 | ); 20 | name = "SwiftUIRules-All"; 21 | productName = "SwiftUIRules-All"; 22 | }; 23 | /* End PBXAggregateTarget section */ 24 | 25 | /* Begin PBXBuildFile section */ 26 | E84699C823194CA800794065 /* libSwiftUIRules-Mobile.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E8A02CDD231948FE00C1D879 /* libSwiftUIRules-Mobile.a */; }; 27 | E84699CF23194DC300794065 /* TestEnvironmentKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84699CE23194DC300794065 /* TestEnvironmentKeys.swift */; }; 28 | E87A57592319537E004EFF40 /* SwiftUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87A57582319537D004EFF40 /* SwiftUITests.swift */; }; 29 | E8A02CC5231948FE00C1D879 /* RuleKeyPathPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D3231933EF0055A9F9 /* RuleKeyPathPredicate.swift */; }; 30 | E8A02CC6231948FE00C1D879 /* RuleContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13DE231933EF0055A9F9 /* RuleContext.swift */; }; 31 | E8A02CC7231948FE00C1D879 /* DynamicEnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E6231933EF0055A9F9 /* DynamicEnvironmentValues.swift */; }; 32 | E8A02CC8231948FE00C1D879 /* RuleModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D5231933EF0055A9F9 /* RuleModel.swift */; }; 33 | E8A02CC9231948FE00C1D879 /* RuleDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E0231933EF0055A9F9 /* RuleDebug.swift */; }; 34 | E8A02CCA231948FE00C1D879 /* DynamicEnvironmentKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E3231933EF0055A9F9 /* DynamicEnvironmentKey.swift */; }; 35 | E8A02CCB231948FE00C1D879 /* RuleCompoundPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D1231933EF0055A9F9 /* RuleCompoundPredicate.swift */; }; 36 | E8A02CCC231948FE00C1D879 /* RuleTypeIDPathAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D9231933EF0055A9F9 /* RuleTypeIDPathAssignment.swift */; }; 37 | E8A02CCD231948FE00C1D879 /* RuleValueAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D7231933EF0055A9F9 /* RuleValueAssignment.swift */; }; 38 | E8A02CCE231948FE00C1D879 /* RuleClosurePredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D0231933EF0055A9F9 /* RuleClosurePredicate.swift */; }; 39 | E8A02CCF231948FE00C1D879 /* RuleTypeIDAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D8231933EF0055A9F9 /* RuleTypeIDAssignment.swift */; }; 40 | E8A02CD0231948FE00C1D879 /* RuleCandidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13DA231933EF0055A9F9 /* RuleCandidate.swift */; }; 41 | E8A02CD2231948FE00C1D879 /* RuleAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13DD231933EF0055A9F9 /* RuleAction.swift */; }; 42 | E8A02CD3231948FE00C1D879 /* RulePredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D2231933EF0055A9F9 /* RulePredicate.swift */; }; 43 | E8A02CD4231948FE00C1D879 /* DynamicEnvironmentPathes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E4231933EF0055A9F9 /* DynamicEnvironmentPathes.swift */; }; 44 | E8A02CD5231948FE00C1D879 /* Rule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E1231933EF0055A9F9 /* Rule.swift */; }; 45 | E8A02CD6231948FE00C1D879 /* RuleOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E7231933EF0055A9F9 /* RuleOperators.swift */; }; 46 | E8A02CD7231948FE00C1D879 /* RuleBoolPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D4231933EF0055A9F9 /* RuleBoolPredicate.swift */; }; 47 | E8A02CD8231948FE00C1D879 /* RuleKeyAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13DB231933EF0055A9F9 /* RuleKeyAssignment.swift */; }; 48 | E8A02CE12319493D00C1D879 /* RuleKeyPathPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D3231933EF0055A9F9 /* RuleKeyPathPredicate.swift */; }; 49 | E8A02CE22319493D00C1D879 /* RuleContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13DE231933EF0055A9F9 /* RuleContext.swift */; }; 50 | E8A02CE32319493D00C1D879 /* DynamicEnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E6231933EF0055A9F9 /* DynamicEnvironmentValues.swift */; }; 51 | E8A02CE42319493D00C1D879 /* RuleModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D5231933EF0055A9F9 /* RuleModel.swift */; }; 52 | E8A02CE52319493D00C1D879 /* RuleDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E0231933EF0055A9F9 /* RuleDebug.swift */; }; 53 | E8A02CE62319493D00C1D879 /* DynamicEnvironmentKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E3231933EF0055A9F9 /* DynamicEnvironmentKey.swift */; }; 54 | E8A02CE72319493D00C1D879 /* RuleCompoundPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D1231933EF0055A9F9 /* RuleCompoundPredicate.swift */; }; 55 | E8A02CE82319493D00C1D879 /* RuleTypeIDPathAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D9231933EF0055A9F9 /* RuleTypeIDPathAssignment.swift */; }; 56 | E8A02CE92319493D00C1D879 /* RuleValueAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D7231933EF0055A9F9 /* RuleValueAssignment.swift */; }; 57 | E8A02CEA2319493D00C1D879 /* RuleClosurePredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D0231933EF0055A9F9 /* RuleClosurePredicate.swift */; }; 58 | E8A02CEB2319493D00C1D879 /* RuleTypeIDAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D8231933EF0055A9F9 /* RuleTypeIDAssignment.swift */; }; 59 | E8A02CEC2319493D00C1D879 /* RuleCandidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13DA231933EF0055A9F9 /* RuleCandidate.swift */; }; 60 | E8A02CEE2319493D00C1D879 /* RuleAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13DD231933EF0055A9F9 /* RuleAction.swift */; }; 61 | E8A02CEF2319493D00C1D879 /* RulePredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D2231933EF0055A9F9 /* RulePredicate.swift */; }; 62 | E8A02CF02319493D00C1D879 /* DynamicEnvironmentPathes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E4231933EF0055A9F9 /* DynamicEnvironmentPathes.swift */; }; 63 | E8A02CF12319493D00C1D879 /* Rule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E1231933EF0055A9F9 /* Rule.swift */; }; 64 | E8A02CF22319493D00C1D879 /* RuleOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E7231933EF0055A9F9 /* RuleOperators.swift */; }; 65 | E8A02CF32319493D00C1D879 /* RuleBoolPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D4231933EF0055A9F9 /* RuleBoolPredicate.swift */; }; 66 | E8A02CF42319493D00C1D879 /* RuleKeyAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13DB231933EF0055A9F9 /* RuleKeyAssignment.swift */; }; 67 | E8B6016E231C002000E31BEC /* KeyPathPredicateOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8B6016D231C002000E31BEC /* KeyPathPredicateOperators.swift */; }; 68 | E8B6016F231C002000E31BEC /* KeyPathPredicateOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8B6016D231C002000E31BEC /* KeyPathPredicateOperators.swift */; }; 69 | E8B60170231C002000E31BEC /* KeyPathPredicateOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8B6016D231C002000E31BEC /* KeyPathPredicateOperators.swift */; }; 70 | E8B60173231C011000E31BEC /* CompoundPredicateOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8B60172231C011000E31BEC /* CompoundPredicateOperators.swift */; }; 71 | E8B60174231C011000E31BEC /* CompoundPredicateOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8B60172231C011000E31BEC /* CompoundPredicateOperators.swift */; }; 72 | E8B60175231C011000E31BEC /* CompoundPredicateOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8B60172231C011000E31BEC /* CompoundPredicateOperators.swift */; }; 73 | E8B60177231C018D00E31BEC /* AssignmentOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8B60176231C018D00E31BEC /* AssignmentOperators.swift */; }; 74 | E8B60178231C018D00E31BEC /* AssignmentOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8B60176231C018D00E31BEC /* AssignmentOperators.swift */; }; 75 | E8B60179231C018D00E31BEC /* AssignmentOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8B60176231C018D00E31BEC /* AssignmentOperators.swift */; }; 76 | E8FA13E8231933EF0055A9F9 /* RuleClosurePredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D0231933EF0055A9F9 /* RuleClosurePredicate.swift */; }; 77 | E8FA13E9231933EF0055A9F9 /* RuleCompoundPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D1231933EF0055A9F9 /* RuleCompoundPredicate.swift */; }; 78 | E8FA13EA231933EF0055A9F9 /* RulePredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D2231933EF0055A9F9 /* RulePredicate.swift */; }; 79 | E8FA13EB231933EF0055A9F9 /* RuleKeyPathPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D3231933EF0055A9F9 /* RuleKeyPathPredicate.swift */; }; 80 | E8FA13EC231933EF0055A9F9 /* RuleBoolPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D4231933EF0055A9F9 /* RuleBoolPredicate.swift */; }; 81 | E8FA13ED231933EF0055A9F9 /* RuleModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D5231933EF0055A9F9 /* RuleModel.swift */; }; 82 | E8FA13EE231933EF0055A9F9 /* RuleValueAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D7231933EF0055A9F9 /* RuleValueAssignment.swift */; }; 83 | E8FA13EF231933EF0055A9F9 /* RuleTypeIDAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D8231933EF0055A9F9 /* RuleTypeIDAssignment.swift */; }; 84 | E8FA13F0231933EF0055A9F9 /* RuleTypeIDPathAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13D9231933EF0055A9F9 /* RuleTypeIDPathAssignment.swift */; }; 85 | E8FA13F1231933EF0055A9F9 /* RuleCandidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13DA231933EF0055A9F9 /* RuleCandidate.swift */; }; 86 | E8FA13F2231933EF0055A9F9 /* RuleKeyAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13DB231933EF0055A9F9 /* RuleKeyAssignment.swift */; }; 87 | E8FA13F4231933EF0055A9F9 /* RuleAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13DD231933EF0055A9F9 /* RuleAction.swift */; }; 88 | E8FA13F5231933EF0055A9F9 /* RuleContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13DE231933EF0055A9F9 /* RuleContext.swift */; }; 89 | E8FA13F6231933EF0055A9F9 /* RuleDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E0231933EF0055A9F9 /* RuleDebug.swift */; }; 90 | E8FA13F7231933EF0055A9F9 /* Rule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E1231933EF0055A9F9 /* Rule.swift */; }; 91 | E8FA13F8231933EF0055A9F9 /* DynamicEnvironmentKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E3231933EF0055A9F9 /* DynamicEnvironmentKey.swift */; }; 92 | E8FA13F9231933EF0055A9F9 /* DynamicEnvironmentPathes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E4231933EF0055A9F9 /* DynamicEnvironmentPathes.swift */; }; 93 | E8FA13FA231933EF0055A9F9 /* DynamicEnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E6231933EF0055A9F9 /* DynamicEnvironmentValues.swift */; }; 94 | E8FA13FB231933EF0055A9F9 /* RuleOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FA13E7231933EF0055A9F9 /* RuleOperators.swift */; }; 95 | /* End PBXBuildFile section */ 96 | 97 | /* Begin PBXContainerItemProxy section */ 98 | E84699C923194CA800794065 /* PBXContainerItemProxy */ = { 99 | isa = PBXContainerItemProxy; 100 | containerPortal = E8FA13BF231926E50055A9F9 /* Project object */; 101 | proxyType = 1; 102 | remoteGlobalIDString = E8A02CC2231948FE00C1D879; 103 | remoteInfo = "SwiftUIRules-Mobile"; 104 | }; 105 | E8A02CFE2319496100C1D879 /* PBXContainerItemProxy */ = { 106 | isa = PBXContainerItemProxy; 107 | containerPortal = E8FA13BF231926E50055A9F9 /* Project object */; 108 | proxyType = 1; 109 | remoteGlobalIDString = E8FA13C8231929B40055A9F9; 110 | remoteInfo = "SwiftUIRules-Mac"; 111 | }; 112 | E8A02D002319496100C1D879 /* PBXContainerItemProxy */ = { 113 | isa = PBXContainerItemProxy; 114 | containerPortal = E8FA13BF231926E50055A9F9 /* Project object */; 115 | proxyType = 1; 116 | remoteGlobalIDString = E8A02CC2231948FE00C1D879; 117 | remoteInfo = "SwiftUIRules-Mobile"; 118 | }; 119 | E8A02D022319496100C1D879 /* PBXContainerItemProxy */ = { 120 | isa = PBXContainerItemProxy; 121 | containerPortal = E8FA13BF231926E50055A9F9 /* Project object */; 122 | proxyType = 1; 123 | remoteGlobalIDString = E8A02CDE2319493D00C1D879; 124 | remoteInfo = "SwiftUIRules-Watch"; 125 | }; 126 | /* End PBXContainerItemProxy section */ 127 | 128 | /* Begin PBXFileReference section */ 129 | E84699C323194CA800794065 /* SwiftUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 130 | E84699C723194CA800794065 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = Info.plist; path = Tests/SwiftUITests/Info.plist; sourceTree = ""; }; 131 | E84699CE23194DC300794065 /* TestEnvironmentKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TestEnvironmentKeys.swift; path = Tests/SwiftUITests/TestEnvironmentKeys.swift; sourceTree = SOURCE_ROOT; }; 132 | E87A57582319537D004EFF40 /* SwiftUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftUITests.swift; path = Tests/SwiftUITests/SwiftUITests.swift; sourceTree = SOURCE_ROOT; }; 133 | E8A02CC02319442300C1D879 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 134 | E8A02CC12319446200C1D879 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 135 | E8A02CDD231948FE00C1D879 /* libSwiftUIRules-Mobile.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libSwiftUIRules-Mobile.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 136 | E8A02CF92319493D00C1D879 /* libSwiftUIRules-Watch.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libSwiftUIRules-Watch.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 137 | E8B6016D231C002000E31BEC /* KeyPathPredicateOperators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPathPredicateOperators.swift; sourceTree = ""; }; 138 | E8B60171231C003F00E31BEC /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 139 | E8B60172231C011000E31BEC /* CompoundPredicateOperators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompoundPredicateOperators.swift; sourceTree = ""; }; 140 | E8B60176231C018D00E31BEC /* AssignmentOperators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssignmentOperators.swift; sourceTree = ""; }; 141 | E8FA13C9231929B40055A9F9 /* libSwiftUIRules-Mac.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libSwiftUIRules-Mac.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 142 | E8FA13D0231933EF0055A9F9 /* RuleClosurePredicate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleClosurePredicate.swift; sourceTree = ""; }; 143 | E8FA13D1231933EF0055A9F9 /* RuleCompoundPredicate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleCompoundPredicate.swift; sourceTree = ""; }; 144 | E8FA13D2231933EF0055A9F9 /* RulePredicate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RulePredicate.swift; sourceTree = ""; }; 145 | E8FA13D3231933EF0055A9F9 /* RuleKeyPathPredicate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleKeyPathPredicate.swift; sourceTree = ""; }; 146 | E8FA13D4231933EF0055A9F9 /* RuleBoolPredicate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleBoolPredicate.swift; sourceTree = ""; }; 147 | E8FA13D5231933EF0055A9F9 /* RuleModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleModel.swift; sourceTree = ""; }; 148 | E8FA13D7231933EF0055A9F9 /* RuleValueAssignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleValueAssignment.swift; sourceTree = ""; }; 149 | E8FA13D8231933EF0055A9F9 /* RuleTypeIDAssignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleTypeIDAssignment.swift; sourceTree = ""; }; 150 | E8FA13D9231933EF0055A9F9 /* RuleTypeIDPathAssignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleTypeIDPathAssignment.swift; sourceTree = ""; }; 151 | E8FA13DA231933EF0055A9F9 /* RuleCandidate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleCandidate.swift; sourceTree = ""; }; 152 | E8FA13DB231933EF0055A9F9 /* RuleKeyAssignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleKeyAssignment.swift; sourceTree = ""; }; 153 | E8FA13DD231933EF0055A9F9 /* RuleAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleAction.swift; sourceTree = ""; }; 154 | E8FA13DE231933EF0055A9F9 /* RuleContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleContext.swift; sourceTree = ""; }; 155 | E8FA13E0231933EF0055A9F9 /* RuleDebug.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleDebug.swift; sourceTree = ""; }; 156 | E8FA13E1231933EF0055A9F9 /* Rule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Rule.swift; sourceTree = ""; }; 157 | E8FA13E3231933EF0055A9F9 /* DynamicEnvironmentKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicEnvironmentKey.swift; sourceTree = ""; }; 158 | E8FA13E4231933EF0055A9F9 /* DynamicEnvironmentPathes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicEnvironmentPathes.swift; sourceTree = ""; }; 159 | E8FA13E5231933EF0055A9F9 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 160 | E8FA13E6231933EF0055A9F9 /* DynamicEnvironmentValues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicEnvironmentValues.swift; sourceTree = ""; }; 161 | E8FA13E7231933EF0055A9F9 /* RuleOperators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleOperators.swift; sourceTree = ""; }; 162 | E8FA13FC231933F50055A9F9 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 163 | E8FA13FD231933FA0055A9F9 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 164 | E8FA13FF2319340A0055A9F9 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = ""; }; 165 | /* End PBXFileReference section */ 166 | 167 | /* Begin PBXFrameworksBuildPhase section */ 168 | E84699C023194CA800794065 /* Frameworks */ = { 169 | isa = PBXFrameworksBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | E84699C823194CA800794065 /* libSwiftUIRules-Mobile.a in Frameworks */, 173 | ); 174 | runOnlyForDeploymentPostprocessing = 0; 175 | }; 176 | E8A02CD9231948FE00C1D879 /* Frameworks */ = { 177 | isa = PBXFrameworksBuildPhase; 178 | buildActionMask = 2147483647; 179 | files = ( 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | E8A02CF52319493D00C1D879 /* Frameworks */ = { 184 | isa = PBXFrameworksBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | ); 188 | runOnlyForDeploymentPostprocessing = 0; 189 | }; 190 | E8FA13C7231929B40055A9F9 /* Frameworks */ = { 191 | isa = PBXFrameworksBuildPhase; 192 | buildActionMask = 2147483647; 193 | files = ( 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | /* End PBXFrameworksBuildPhase section */ 198 | 199 | /* Begin PBXGroup section */ 200 | E84699C423194CA800794065 /* SwiftUITests */ = { 201 | isa = PBXGroup; 202 | children = ( 203 | E84699CE23194DC300794065 /* TestEnvironmentKeys.swift */, 204 | E87A57582319537D004EFF40 /* SwiftUITests.swift */, 205 | E84699C723194CA800794065 /* Info.plist */, 206 | ); 207 | path = SwiftUITests; 208 | sourceTree = ""; 209 | }; 210 | E8B6016C231C000B00E31BEC /* Operators */ = { 211 | isa = PBXGroup; 212 | children = ( 213 | E8B60171231C003F00E31BEC /* README.md */, 214 | E8FA13E7231933EF0055A9F9 /* RuleOperators.swift */, 215 | E8B6016D231C002000E31BEC /* KeyPathPredicateOperators.swift */, 216 | E8B60172231C011000E31BEC /* CompoundPredicateOperators.swift */, 217 | E8B60176231C018D00E31BEC /* AssignmentOperators.swift */, 218 | ); 219 | path = Operators; 220 | sourceTree = ""; 221 | }; 222 | E8FA13BE231926E50055A9F9 = { 223 | isa = PBXGroup; 224 | children = ( 225 | E8FA13FC231933F50055A9F9 /* README.md */, 226 | E8FA13FD231933FA0055A9F9 /* Package.swift */, 227 | E8FA13CE231933EF0055A9F9 /* SwiftUIRules */, 228 | E8FA13FE2319340A0055A9F9 /* xcconfig */, 229 | E84699C423194CA800794065 /* SwiftUITests */, 230 | E8FA13CA231929B40055A9F9 /* Products */, 231 | ); 232 | sourceTree = ""; 233 | }; 234 | E8FA13CA231929B40055A9F9 /* Products */ = { 235 | isa = PBXGroup; 236 | children = ( 237 | E8FA13C9231929B40055A9F9 /* libSwiftUIRules-Mac.a */, 238 | E8A02CDD231948FE00C1D879 /* libSwiftUIRules-Mobile.a */, 239 | E8A02CF92319493D00C1D879 /* libSwiftUIRules-Watch.a */, 240 | E84699C323194CA800794065 /* SwiftUITests.xctest */, 241 | ); 242 | name = Products; 243 | sourceTree = ""; 244 | }; 245 | E8FA13CE231933EF0055A9F9 /* SwiftUIRules */ = { 246 | isa = PBXGroup; 247 | children = ( 248 | E8FA13DE231933EF0055A9F9 /* RuleContext.swift */, 249 | E8FA13D5231933EF0055A9F9 /* RuleModel.swift */, 250 | E8FA13E1231933EF0055A9F9 /* Rule.swift */, 251 | E8B6016C231C000B00E31BEC /* Operators */, 252 | E8FA13E2231933EF0055A9F9 /* DynamicEnvironment */, 253 | E8FA13CF231933EF0055A9F9 /* Predicates */, 254 | E8FA13D6231933EF0055A9F9 /* Assignments */, 255 | E8FA13DF231933EF0055A9F9 /* Support */, 256 | ); 257 | name = SwiftUIRules; 258 | path = Sources/SwiftUIRules; 259 | sourceTree = ""; 260 | }; 261 | E8FA13CF231933EF0055A9F9 /* Predicates */ = { 262 | isa = PBXGroup; 263 | children = ( 264 | E8A02CC02319442300C1D879 /* README.md */, 265 | E8FA13D2231933EF0055A9F9 /* RulePredicate.swift */, 266 | E8FA13D1231933EF0055A9F9 /* RuleCompoundPredicate.swift */, 267 | E8FA13D3231933EF0055A9F9 /* RuleKeyPathPredicate.swift */, 268 | E8FA13D4231933EF0055A9F9 /* RuleBoolPredicate.swift */, 269 | E8FA13D0231933EF0055A9F9 /* RuleClosurePredicate.swift */, 270 | ); 271 | path = Predicates; 272 | sourceTree = ""; 273 | }; 274 | E8FA13D6231933EF0055A9F9 /* Assignments */ = { 275 | isa = PBXGroup; 276 | children = ( 277 | E8A02CC12319446200C1D879 /* README.md */, 278 | E8FA13DA231933EF0055A9F9 /* RuleCandidate.swift */, 279 | E8FA13DD231933EF0055A9F9 /* RuleAction.swift */, 280 | E8FA13D8231933EF0055A9F9 /* RuleTypeIDAssignment.swift */, 281 | E8FA13D9231933EF0055A9F9 /* RuleTypeIDPathAssignment.swift */, 282 | E8FA13D7231933EF0055A9F9 /* RuleValueAssignment.swift */, 283 | E8FA13DB231933EF0055A9F9 /* RuleKeyAssignment.swift */, 284 | ); 285 | path = Assignments; 286 | sourceTree = ""; 287 | }; 288 | E8FA13DF231933EF0055A9F9 /* Support */ = { 289 | isa = PBXGroup; 290 | children = ( 291 | E8FA13E0231933EF0055A9F9 /* RuleDebug.swift */, 292 | ); 293 | path = Support; 294 | sourceTree = ""; 295 | }; 296 | E8FA13E2231933EF0055A9F9 /* DynamicEnvironment */ = { 297 | isa = PBXGroup; 298 | children = ( 299 | E8FA13E5231933EF0055A9F9 /* README.md */, 300 | E8FA13E3231933EF0055A9F9 /* DynamicEnvironmentKey.swift */, 301 | E8FA13E4231933EF0055A9F9 /* DynamicEnvironmentPathes.swift */, 302 | E8FA13E6231933EF0055A9F9 /* DynamicEnvironmentValues.swift */, 303 | ); 304 | path = DynamicEnvironment; 305 | sourceTree = ""; 306 | }; 307 | E8FA13FE2319340A0055A9F9 /* xcconfig */ = { 308 | isa = PBXGroup; 309 | children = ( 310 | E8FA13FF2319340A0055A9F9 /* Base.xcconfig */, 311 | ); 312 | path = xcconfig; 313 | sourceTree = ""; 314 | }; 315 | /* End PBXGroup section */ 316 | 317 | /* Begin PBXHeadersBuildPhase section */ 318 | E8A02CC3231948FE00C1D879 /* Headers */ = { 319 | isa = PBXHeadersBuildPhase; 320 | buildActionMask = 2147483647; 321 | files = ( 322 | ); 323 | runOnlyForDeploymentPostprocessing = 0; 324 | }; 325 | E8A02CDF2319493D00C1D879 /* Headers */ = { 326 | isa = PBXHeadersBuildPhase; 327 | buildActionMask = 2147483647; 328 | files = ( 329 | ); 330 | runOnlyForDeploymentPostprocessing = 0; 331 | }; 332 | E8FA13C5231929B40055A9F9 /* Headers */ = { 333 | isa = PBXHeadersBuildPhase; 334 | buildActionMask = 2147483647; 335 | files = ( 336 | ); 337 | runOnlyForDeploymentPostprocessing = 0; 338 | }; 339 | /* End PBXHeadersBuildPhase section */ 340 | 341 | /* Begin PBXNativeTarget section */ 342 | E84699C223194CA800794065 /* SwiftUITests */ = { 343 | isa = PBXNativeTarget; 344 | buildConfigurationList = E84699CD23194CA800794065 /* Build configuration list for PBXNativeTarget "SwiftUITests" */; 345 | buildPhases = ( 346 | E84699BF23194CA800794065 /* Sources */, 347 | E84699C023194CA800794065 /* Frameworks */, 348 | E84699C123194CA800794065 /* Resources */, 349 | ); 350 | buildRules = ( 351 | ); 352 | dependencies = ( 353 | E84699CA23194CA800794065 /* PBXTargetDependency */, 354 | ); 355 | name = SwiftUITests; 356 | productName = SwiftUITests; 357 | productReference = E84699C323194CA800794065 /* SwiftUITests.xctest */; 358 | productType = "com.apple.product-type.bundle.unit-test"; 359 | }; 360 | E8A02CC2231948FE00C1D879 /* SwiftUIRules-Mobile */ = { 361 | isa = PBXNativeTarget; 362 | buildConfigurationList = E8A02CDA231948FE00C1D879 /* Build configuration list for PBXNativeTarget "SwiftUIRules-Mobile" */; 363 | buildPhases = ( 364 | E8A02CC3231948FE00C1D879 /* Headers */, 365 | E8A02CC4231948FE00C1D879 /* Sources */, 366 | E8A02CD9231948FE00C1D879 /* Frameworks */, 367 | ); 368 | buildRules = ( 369 | ); 370 | dependencies = ( 371 | ); 372 | name = "SwiftUIRules-Mobile"; 373 | productName = SwiftUIRules; 374 | productReference = E8A02CDD231948FE00C1D879 /* libSwiftUIRules-Mobile.a */; 375 | productType = "com.apple.product-type.library.static"; 376 | }; 377 | E8A02CDE2319493D00C1D879 /* SwiftUIRules-Watch */ = { 378 | isa = PBXNativeTarget; 379 | buildConfigurationList = E8A02CF62319493D00C1D879 /* Build configuration list for PBXNativeTarget "SwiftUIRules-Watch" */; 380 | buildPhases = ( 381 | E8A02CDF2319493D00C1D879 /* Headers */, 382 | E8A02CE02319493D00C1D879 /* Sources */, 383 | E8A02CF52319493D00C1D879 /* Frameworks */, 384 | ); 385 | buildRules = ( 386 | ); 387 | dependencies = ( 388 | ); 389 | name = "SwiftUIRules-Watch"; 390 | productName = SwiftUIRules; 391 | productReference = E8A02CF92319493D00C1D879 /* libSwiftUIRules-Watch.a */; 392 | productType = "com.apple.product-type.library.static"; 393 | }; 394 | E8FA13C8231929B40055A9F9 /* SwiftUIRules-Mac */ = { 395 | isa = PBXNativeTarget; 396 | buildConfigurationList = E8FA13CB231929B40055A9F9 /* Build configuration list for PBXNativeTarget "SwiftUIRules-Mac" */; 397 | buildPhases = ( 398 | E8FA13C5231929B40055A9F9 /* Headers */, 399 | E8FA13C6231929B40055A9F9 /* Sources */, 400 | E8FA13C7231929B40055A9F9 /* Frameworks */, 401 | ); 402 | buildRules = ( 403 | ); 404 | dependencies = ( 405 | ); 406 | name = "SwiftUIRules-Mac"; 407 | productName = SwiftUIRules; 408 | productReference = E8FA13C9231929B40055A9F9 /* libSwiftUIRules-Mac.a */; 409 | productType = "com.apple.product-type.library.static"; 410 | }; 411 | /* End PBXNativeTarget section */ 412 | 413 | /* Begin PBXProject section */ 414 | E8FA13BF231926E50055A9F9 /* Project object */ = { 415 | isa = PBXProject; 416 | attributes = { 417 | LastSwiftUpdateCheck = 1100; 418 | LastUpgradeCheck = 1100; 419 | TargetAttributes = { 420 | E84699C223194CA800794065 = { 421 | CreatedOnToolsVersion = 11.0; 422 | }; 423 | E8A02CFA2319495800C1D879 = { 424 | CreatedOnToolsVersion = 11.0; 425 | }; 426 | E8FA13C8231929B40055A9F9 = { 427 | CreatedOnToolsVersion = 11.0; 428 | }; 429 | }; 430 | }; 431 | buildConfigurationList = E8FA13C2231926E50055A9F9 /* Build configuration list for PBXProject "SwiftUIRules" */; 432 | compatibilityVersion = "Xcode 9.3"; 433 | developmentRegion = en; 434 | hasScannedForEncodings = 0; 435 | knownRegions = ( 436 | en, 437 | Base, 438 | ); 439 | mainGroup = E8FA13BE231926E50055A9F9; 440 | productRefGroup = E8FA13CA231929B40055A9F9 /* Products */; 441 | projectDirPath = ""; 442 | projectRoot = ""; 443 | targets = ( 444 | E8FA13C8231929B40055A9F9 /* SwiftUIRules-Mac */, 445 | E8A02CC2231948FE00C1D879 /* SwiftUIRules-Mobile */, 446 | E8A02CDE2319493D00C1D879 /* SwiftUIRules-Watch */, 447 | E8A02CFA2319495800C1D879 /* SwiftUIRules-All */, 448 | E84699C223194CA800794065 /* SwiftUITests */, 449 | ); 450 | }; 451 | /* End PBXProject section */ 452 | 453 | /* Begin PBXResourcesBuildPhase section */ 454 | E84699C123194CA800794065 /* Resources */ = { 455 | isa = PBXResourcesBuildPhase; 456 | buildActionMask = 2147483647; 457 | files = ( 458 | ); 459 | runOnlyForDeploymentPostprocessing = 0; 460 | }; 461 | /* End PBXResourcesBuildPhase section */ 462 | 463 | /* Begin PBXSourcesBuildPhase section */ 464 | E84699BF23194CA800794065 /* Sources */ = { 465 | isa = PBXSourcesBuildPhase; 466 | buildActionMask = 2147483647; 467 | files = ( 468 | E84699CF23194DC300794065 /* TestEnvironmentKeys.swift in Sources */, 469 | E87A57592319537E004EFF40 /* SwiftUITests.swift in Sources */, 470 | ); 471 | runOnlyForDeploymentPostprocessing = 0; 472 | }; 473 | E8A02CC4231948FE00C1D879 /* Sources */ = { 474 | isa = PBXSourcesBuildPhase; 475 | buildActionMask = 2147483647; 476 | files = ( 477 | E8A02CC5231948FE00C1D879 /* RuleKeyPathPredicate.swift in Sources */, 478 | E8A02CC6231948FE00C1D879 /* RuleContext.swift in Sources */, 479 | E8B60178231C018D00E31BEC /* AssignmentOperators.swift in Sources */, 480 | E8A02CC7231948FE00C1D879 /* DynamicEnvironmentValues.swift in Sources */, 481 | E8A02CC8231948FE00C1D879 /* RuleModel.swift in Sources */, 482 | E8A02CC9231948FE00C1D879 /* RuleDebug.swift in Sources */, 483 | E8A02CCA231948FE00C1D879 /* DynamicEnvironmentKey.swift in Sources */, 484 | E8A02CCB231948FE00C1D879 /* RuleCompoundPredicate.swift in Sources */, 485 | E8A02CCC231948FE00C1D879 /* RuleTypeIDPathAssignment.swift in Sources */, 486 | E8A02CCD231948FE00C1D879 /* RuleValueAssignment.swift in Sources */, 487 | E8A02CCE231948FE00C1D879 /* RuleClosurePredicate.swift in Sources */, 488 | E8B60174231C011000E31BEC /* CompoundPredicateOperators.swift in Sources */, 489 | E8A02CCF231948FE00C1D879 /* RuleTypeIDAssignment.swift in Sources */, 490 | E8A02CD0231948FE00C1D879 /* RuleCandidate.swift in Sources */, 491 | E8A02CD2231948FE00C1D879 /* RuleAction.swift in Sources */, 492 | E8A02CD3231948FE00C1D879 /* RulePredicate.swift in Sources */, 493 | E8B6016F231C002000E31BEC /* KeyPathPredicateOperators.swift in Sources */, 494 | E8A02CD4231948FE00C1D879 /* DynamicEnvironmentPathes.swift in Sources */, 495 | E8A02CD5231948FE00C1D879 /* Rule.swift in Sources */, 496 | E8A02CD6231948FE00C1D879 /* RuleOperators.swift in Sources */, 497 | E8A02CD7231948FE00C1D879 /* RuleBoolPredicate.swift in Sources */, 498 | E8A02CD8231948FE00C1D879 /* RuleKeyAssignment.swift in Sources */, 499 | ); 500 | runOnlyForDeploymentPostprocessing = 0; 501 | }; 502 | E8A02CE02319493D00C1D879 /* Sources */ = { 503 | isa = PBXSourcesBuildPhase; 504 | buildActionMask = 2147483647; 505 | files = ( 506 | E8A02CE12319493D00C1D879 /* RuleKeyPathPredicate.swift in Sources */, 507 | E8A02CE22319493D00C1D879 /* RuleContext.swift in Sources */, 508 | E8B60179231C018D00E31BEC /* AssignmentOperators.swift in Sources */, 509 | E8A02CE32319493D00C1D879 /* DynamicEnvironmentValues.swift in Sources */, 510 | E8A02CE42319493D00C1D879 /* RuleModel.swift in Sources */, 511 | E8A02CE52319493D00C1D879 /* RuleDebug.swift in Sources */, 512 | E8A02CE62319493D00C1D879 /* DynamicEnvironmentKey.swift in Sources */, 513 | E8A02CE72319493D00C1D879 /* RuleCompoundPredicate.swift in Sources */, 514 | E8A02CE82319493D00C1D879 /* RuleTypeIDPathAssignment.swift in Sources */, 515 | E8A02CE92319493D00C1D879 /* RuleValueAssignment.swift in Sources */, 516 | E8A02CEA2319493D00C1D879 /* RuleClosurePredicate.swift in Sources */, 517 | E8B60175231C011000E31BEC /* CompoundPredicateOperators.swift in Sources */, 518 | E8A02CEB2319493D00C1D879 /* RuleTypeIDAssignment.swift in Sources */, 519 | E8A02CEC2319493D00C1D879 /* RuleCandidate.swift in Sources */, 520 | E8A02CEE2319493D00C1D879 /* RuleAction.swift in Sources */, 521 | E8A02CEF2319493D00C1D879 /* RulePredicate.swift in Sources */, 522 | E8B60170231C002000E31BEC /* KeyPathPredicateOperators.swift in Sources */, 523 | E8A02CF02319493D00C1D879 /* DynamicEnvironmentPathes.swift in Sources */, 524 | E8A02CF12319493D00C1D879 /* Rule.swift in Sources */, 525 | E8A02CF22319493D00C1D879 /* RuleOperators.swift in Sources */, 526 | E8A02CF32319493D00C1D879 /* RuleBoolPredicate.swift in Sources */, 527 | E8A02CF42319493D00C1D879 /* RuleKeyAssignment.swift in Sources */, 528 | ); 529 | runOnlyForDeploymentPostprocessing = 0; 530 | }; 531 | E8FA13C6231929B40055A9F9 /* Sources */ = { 532 | isa = PBXSourcesBuildPhase; 533 | buildActionMask = 2147483647; 534 | files = ( 535 | E8FA13EB231933EF0055A9F9 /* RuleKeyPathPredicate.swift in Sources */, 536 | E8FA13F5231933EF0055A9F9 /* RuleContext.swift in Sources */, 537 | E8B60177231C018D00E31BEC /* AssignmentOperators.swift in Sources */, 538 | E8FA13FA231933EF0055A9F9 /* DynamicEnvironmentValues.swift in Sources */, 539 | E8FA13ED231933EF0055A9F9 /* RuleModel.swift in Sources */, 540 | E8FA13F6231933EF0055A9F9 /* RuleDebug.swift in Sources */, 541 | E8FA13F8231933EF0055A9F9 /* DynamicEnvironmentKey.swift in Sources */, 542 | E8FA13E9231933EF0055A9F9 /* RuleCompoundPredicate.swift in Sources */, 543 | E8FA13F0231933EF0055A9F9 /* RuleTypeIDPathAssignment.swift in Sources */, 544 | E8FA13EE231933EF0055A9F9 /* RuleValueAssignment.swift in Sources */, 545 | E8FA13E8231933EF0055A9F9 /* RuleClosurePredicate.swift in Sources */, 546 | E8B60173231C011000E31BEC /* CompoundPredicateOperators.swift in Sources */, 547 | E8FA13EF231933EF0055A9F9 /* RuleTypeIDAssignment.swift in Sources */, 548 | E8FA13F1231933EF0055A9F9 /* RuleCandidate.swift in Sources */, 549 | E8FA13F4231933EF0055A9F9 /* RuleAction.swift in Sources */, 550 | E8FA13EA231933EF0055A9F9 /* RulePredicate.swift in Sources */, 551 | E8B6016E231C002000E31BEC /* KeyPathPredicateOperators.swift in Sources */, 552 | E8FA13F9231933EF0055A9F9 /* DynamicEnvironmentPathes.swift in Sources */, 553 | E8FA13F7231933EF0055A9F9 /* Rule.swift in Sources */, 554 | E8FA13FB231933EF0055A9F9 /* RuleOperators.swift in Sources */, 555 | E8FA13EC231933EF0055A9F9 /* RuleBoolPredicate.swift in Sources */, 556 | E8FA13F2231933EF0055A9F9 /* RuleKeyAssignment.swift in Sources */, 557 | ); 558 | runOnlyForDeploymentPostprocessing = 0; 559 | }; 560 | /* End PBXSourcesBuildPhase section */ 561 | 562 | /* Begin PBXTargetDependency section */ 563 | E84699CA23194CA800794065 /* PBXTargetDependency */ = { 564 | isa = PBXTargetDependency; 565 | target = E8A02CC2231948FE00C1D879 /* SwiftUIRules-Mobile */; 566 | targetProxy = E84699C923194CA800794065 /* PBXContainerItemProxy */; 567 | }; 568 | E8A02CFF2319496100C1D879 /* PBXTargetDependency */ = { 569 | isa = PBXTargetDependency; 570 | target = E8FA13C8231929B40055A9F9 /* SwiftUIRules-Mac */; 571 | targetProxy = E8A02CFE2319496100C1D879 /* PBXContainerItemProxy */; 572 | }; 573 | E8A02D012319496100C1D879 /* PBXTargetDependency */ = { 574 | isa = PBXTargetDependency; 575 | target = E8A02CC2231948FE00C1D879 /* SwiftUIRules-Mobile */; 576 | targetProxy = E8A02D002319496100C1D879 /* PBXContainerItemProxy */; 577 | }; 578 | E8A02D032319496100C1D879 /* PBXTargetDependency */ = { 579 | isa = PBXTargetDependency; 580 | target = E8A02CDE2319493D00C1D879 /* SwiftUIRules-Watch */; 581 | targetProxy = E8A02D022319496100C1D879 /* PBXContainerItemProxy */; 582 | }; 583 | /* End PBXTargetDependency section */ 584 | 585 | /* Begin XCBuildConfiguration section */ 586 | E84699CB23194CA800794065 /* Debug */ = { 587 | isa = XCBuildConfiguration; 588 | buildSettings = { 589 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 590 | CLANG_CXX_LIBRARY = "libc++"; 591 | CODE_SIGN_STYLE = Automatic; 592 | COPY_PHASE_STRIP = NO; 593 | ENABLE_STRICT_OBJC_MSGSEND = YES; 594 | ENABLE_TESTABILITY = YES; 595 | GCC_DYNAMIC_NO_PIC = NO; 596 | GCC_OPTIMIZATION_LEVEL = 0; 597 | GCC_PREPROCESSOR_DEFINITIONS = ( 598 | "DEBUG=1", 599 | "$(inherited)", 600 | ); 601 | INFOPLIST_FILE = Tests/SwiftUITests/Info.plist; 602 | LD_RUNPATH_SEARCH_PATHS = ( 603 | "$(inherited)", 604 | "@executable_path/Frameworks", 605 | "@loader_path/Frameworks", 606 | ); 607 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 608 | MTL_FAST_MATH = YES; 609 | ONLY_ACTIVE_ARCH = YES; 610 | PRODUCT_BUNDLE_IDENTIFIER = de.zeezide.swiftui.rules.SwiftUITests; 611 | PRODUCT_NAME = "$(TARGET_NAME)"; 612 | SDKROOT = iphoneos; 613 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 614 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 615 | TARGETED_DEVICE_FAMILY = "1,2"; 616 | }; 617 | name = Debug; 618 | }; 619 | E84699CC23194CA800794065 /* Release */ = { 620 | isa = XCBuildConfiguration; 621 | buildSettings = { 622 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 623 | CLANG_CXX_LIBRARY = "libc++"; 624 | CODE_SIGN_STYLE = Automatic; 625 | COPY_PHASE_STRIP = NO; 626 | ENABLE_NS_ASSERTIONS = NO; 627 | ENABLE_STRICT_OBJC_MSGSEND = YES; 628 | INFOPLIST_FILE = Tests/SwiftUITests/Info.plist; 629 | LD_RUNPATH_SEARCH_PATHS = ( 630 | "$(inherited)", 631 | "@executable_path/Frameworks", 632 | "@loader_path/Frameworks", 633 | ); 634 | MTL_ENABLE_DEBUG_INFO = NO; 635 | MTL_FAST_MATH = YES; 636 | PRODUCT_BUNDLE_IDENTIFIER = de.zeezide.swiftui.rules.SwiftUITests; 637 | PRODUCT_NAME = "$(TARGET_NAME)"; 638 | SDKROOT = iphoneos; 639 | SWIFT_COMPILATION_MODE = wholemodule; 640 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 641 | TARGETED_DEVICE_FAMILY = "1,2"; 642 | VALIDATE_PRODUCT = YES; 643 | }; 644 | name = Release; 645 | }; 646 | E8A02CDB231948FE00C1D879 /* Debug */ = { 647 | isa = XCBuildConfiguration; 648 | buildSettings = { 649 | CODE_SIGN_STYLE = Automatic; 650 | ENABLE_TESTABILITY = YES; 651 | EXECUTABLE_PREFIX = lib; 652 | GCC_OPTIMIZATION_LEVEL = 0; 653 | GCC_PREPROCESSOR_DEFINITIONS = ( 654 | "DEBUG=1", 655 | "$(inherited)", 656 | ); 657 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 658 | ONLY_ACTIVE_ARCH = YES; 659 | PRODUCT_MODULE_NAME = SwiftUIRules; 660 | PRODUCT_NAME = "$(TARGET_NAME)"; 661 | SDKROOT = iphoneos; 662 | }; 663 | name = Debug; 664 | }; 665 | E8A02CDC231948FE00C1D879 /* Release */ = { 666 | isa = XCBuildConfiguration; 667 | buildSettings = { 668 | CODE_SIGN_STYLE = Automatic; 669 | ENABLE_NS_ASSERTIONS = NO; 670 | EXECUTABLE_PREFIX = lib; 671 | MTL_ENABLE_DEBUG_INFO = NO; 672 | PRODUCT_MODULE_NAME = SwiftUIRules; 673 | PRODUCT_NAME = "$(TARGET_NAME)"; 674 | SDKROOT = iphoneos; 675 | }; 676 | name = Release; 677 | }; 678 | E8A02CF72319493D00C1D879 /* Debug */ = { 679 | isa = XCBuildConfiguration; 680 | buildSettings = { 681 | CODE_SIGN_STYLE = Automatic; 682 | ENABLE_TESTABILITY = YES; 683 | EXECUTABLE_PREFIX = lib; 684 | GCC_OPTIMIZATION_LEVEL = 0; 685 | GCC_PREPROCESSOR_DEFINITIONS = ( 686 | "DEBUG=1", 687 | "$(inherited)", 688 | ); 689 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 690 | ONLY_ACTIVE_ARCH = YES; 691 | PRODUCT_MODULE_NAME = SwiftUIRules; 692 | PRODUCT_NAME = "$(TARGET_NAME)"; 693 | SDKROOT = watchos; 694 | }; 695 | name = Debug; 696 | }; 697 | E8A02CF82319493D00C1D879 /* Release */ = { 698 | isa = XCBuildConfiguration; 699 | buildSettings = { 700 | CODE_SIGN_STYLE = Automatic; 701 | ENABLE_NS_ASSERTIONS = NO; 702 | EXECUTABLE_PREFIX = lib; 703 | MTL_ENABLE_DEBUG_INFO = NO; 704 | PRODUCT_MODULE_NAME = SwiftUIRules; 705 | PRODUCT_NAME = "$(TARGET_NAME)"; 706 | SDKROOT = watchos; 707 | }; 708 | name = Release; 709 | }; 710 | E8A02CFC2319495800C1D879 /* Debug */ = { 711 | isa = XCBuildConfiguration; 712 | buildSettings = { 713 | CODE_SIGN_STYLE = Automatic; 714 | PRODUCT_NAME = "$(TARGET_NAME)"; 715 | }; 716 | name = Debug; 717 | }; 718 | E8A02CFD2319495800C1D879 /* Release */ = { 719 | isa = XCBuildConfiguration; 720 | buildSettings = { 721 | CODE_SIGN_STYLE = Automatic; 722 | PRODUCT_NAME = "$(TARGET_NAME)"; 723 | }; 724 | name = Release; 725 | }; 726 | E8FA13C3231926E50055A9F9 /* Debug */ = { 727 | isa = XCBuildConfiguration; 728 | baseConfigurationReference = E8FA13FF2319340A0055A9F9 /* Base.xcconfig */; 729 | buildSettings = { 730 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 731 | }; 732 | name = Debug; 733 | }; 734 | E8FA13C4231926E50055A9F9 /* Release */ = { 735 | isa = XCBuildConfiguration; 736 | baseConfigurationReference = E8FA13FF2319340A0055A9F9 /* Base.xcconfig */; 737 | buildSettings = { 738 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 739 | }; 740 | name = Release; 741 | }; 742 | E8FA13CC231929B40055A9F9 /* Debug */ = { 743 | isa = XCBuildConfiguration; 744 | buildSettings = { 745 | CODE_SIGN_STYLE = Automatic; 746 | ENABLE_TESTABILITY = YES; 747 | EXECUTABLE_PREFIX = lib; 748 | GCC_OPTIMIZATION_LEVEL = 0; 749 | GCC_PREPROCESSOR_DEFINITIONS = ( 750 | "DEBUG=1", 751 | "$(inherited)", 752 | ); 753 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 754 | ONLY_ACTIVE_ARCH = YES; 755 | PRODUCT_MODULE_NAME = SwiftUIRules; 756 | PRODUCT_NAME = "$(TARGET_NAME)"; 757 | SDKROOT = macosx; 758 | }; 759 | name = Debug; 760 | }; 761 | E8FA13CD231929B40055A9F9 /* Release */ = { 762 | isa = XCBuildConfiguration; 763 | buildSettings = { 764 | CODE_SIGN_STYLE = Automatic; 765 | ENABLE_NS_ASSERTIONS = NO; 766 | EXECUTABLE_PREFIX = lib; 767 | MTL_ENABLE_DEBUG_INFO = NO; 768 | PRODUCT_MODULE_NAME = SwiftUIRules; 769 | PRODUCT_NAME = "$(TARGET_NAME)"; 770 | SDKROOT = macosx; 771 | }; 772 | name = Release; 773 | }; 774 | /* End XCBuildConfiguration section */ 775 | 776 | /* Begin XCConfigurationList section */ 777 | E84699CD23194CA800794065 /* Build configuration list for PBXNativeTarget "SwiftUITests" */ = { 778 | isa = XCConfigurationList; 779 | buildConfigurations = ( 780 | E84699CB23194CA800794065 /* Debug */, 781 | E84699CC23194CA800794065 /* Release */, 782 | ); 783 | defaultConfigurationIsVisible = 0; 784 | defaultConfigurationName = Release; 785 | }; 786 | E8A02CDA231948FE00C1D879 /* Build configuration list for PBXNativeTarget "SwiftUIRules-Mobile" */ = { 787 | isa = XCConfigurationList; 788 | buildConfigurations = ( 789 | E8A02CDB231948FE00C1D879 /* Debug */, 790 | E8A02CDC231948FE00C1D879 /* Release */, 791 | ); 792 | defaultConfigurationIsVisible = 0; 793 | defaultConfigurationName = Release; 794 | }; 795 | E8A02CF62319493D00C1D879 /* Build configuration list for PBXNativeTarget "SwiftUIRules-Watch" */ = { 796 | isa = XCConfigurationList; 797 | buildConfigurations = ( 798 | E8A02CF72319493D00C1D879 /* Debug */, 799 | E8A02CF82319493D00C1D879 /* Release */, 800 | ); 801 | defaultConfigurationIsVisible = 0; 802 | defaultConfigurationName = Release; 803 | }; 804 | E8A02CFB2319495800C1D879 /* Build configuration list for PBXAggregateTarget "SwiftUIRules-All" */ = { 805 | isa = XCConfigurationList; 806 | buildConfigurations = ( 807 | E8A02CFC2319495800C1D879 /* Debug */, 808 | E8A02CFD2319495800C1D879 /* Release */, 809 | ); 810 | defaultConfigurationIsVisible = 0; 811 | defaultConfigurationName = Release; 812 | }; 813 | E8FA13C2231926E50055A9F9 /* Build configuration list for PBXProject "SwiftUIRules" */ = { 814 | isa = XCConfigurationList; 815 | buildConfigurations = ( 816 | E8FA13C3231926E50055A9F9 /* Debug */, 817 | E8FA13C4231926E50055A9F9 /* Release */, 818 | ); 819 | defaultConfigurationIsVisible = 0; 820 | defaultConfigurationName = Release; 821 | }; 822 | E8FA13CB231929B40055A9F9 /* Build configuration list for PBXNativeTarget "SwiftUIRules-Mac" */ = { 823 | isa = XCConfigurationList; 824 | buildConfigurations = ( 825 | E8FA13CC231929B40055A9F9 /* Debug */, 826 | E8FA13CD231929B40055A9F9 /* Release */, 827 | ); 828 | defaultConfigurationIsVisible = 0; 829 | defaultConfigurationName = Release; 830 | }; 831 | /* End XCConfigurationList section */ 832 | }; 833 | rootObject = E8FA13BF231926E50055A9F9 /* Project object */; 834 | } 835 | -------------------------------------------------------------------------------- /SwiftUIRules.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftUIRules.xcodeproj/xcshareddata/xcschemes/SwiftUIRules-Mac.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /SwiftUIRules.xcodeproj/xcshareddata/xcschemes/SwiftUIRules-Mobile.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 67 | 68 | 74 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /SwiftUIRules.xcodeproj/xcshareddata/xcschemes/SwiftUIRules-Watch.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Tests/SwiftUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/SwiftUITests/SwiftUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUITests.swift 3 | // SwiftUITests 4 | // 5 | // Created by Helge Heß on 30.08.19. 6 | // 7 | 8 | import XCTest 9 | @testable import SwiftUIRules 10 | 11 | class SwiftUITests: XCTestCase { 12 | 13 | func testSimpleModelDefaultEnvironment() throws { 14 | let context = RuleContext(ruleModel: [ 15 | \.verb == "view" => \.title <= "Hello!" 16 | ]) 17 | XCTAssertEqual(context.title, "Hello!") 18 | } 19 | 20 | func testSimpleModelCustomEnvironment() throws { 21 | var context = RuleContext(ruleModel: [ 22 | \.verb == "edit" => \.title <= "Work it.", 23 | \.verb == "view" => \.title <= "Hello!" 24 | ]) 25 | context.verb = "edit" 26 | 27 | XCTAssertEqual(context.title, "Work it.") 28 | } 29 | 30 | func testPathCompare() throws { 31 | let context = RuleContext(ruleModel: [ 32 | \.todo.priority == .low => \.color <= .gray, 33 | \.todo.priority == .high => \.color <= .red 34 | ]) 35 | XCTAssertEqual(context.color, .red) 36 | } 37 | 38 | func testRuleComplexity() throws { 39 | let context = RuleContext(ruleModel: [ 40 | \.todo.priority == .high => \.color <= .red, 41 | \.todo.priority == .high && \.verb == "view" => \.color <= .gray 42 | ]) 43 | XCTAssertEqual(context.color, .gray) 44 | } 45 | 46 | func testSimplePathAssignment() throws { 47 | let context = RuleContext(ruleModel: [ \.navigationBarTitle <= \.title ]) 48 | XCTAssertEqual(context.navigationBarTitle, TitleEnvironmentKey.defaultValue) 49 | } 50 | 51 | func testOtherPathAssignment() throws { 52 | let context = RuleContext(ruleModel: [ \.title <= \.verb ]) 53 | XCTAssertEqual(context.title, VerbEnvironmentKey.defaultValue) 54 | } 55 | 56 | func testNestedPathAssignment() throws { 57 | let context = RuleContext(ruleModel: [ 58 | \.navigationBarTitle <= \.title, 59 | \.title <= \.verb 60 | ]) 61 | XCTAssertEqual(context.title, VerbEnvironmentKey.defaultValue) 62 | XCTAssertEqual(context.navigationBarTitle, VerbEnvironmentKey.defaultValue) 63 | } 64 | 65 | func testKeyPath2Predicate() throws { 66 | var context = RuleContext(ruleModel: [ 67 | \.title == \.navigationBarTitle => \.color <= .red, 68 | \.color <= .green 69 | ]) 70 | context.title = "blub" 71 | context.navigationBarTitle = "blub" 72 | XCTAssertEqual(context.color, .red) 73 | } 74 | 75 | func CRASHtestSelfAssignment() throws { 76 | // This results in a regular recursion. It looks like it doesn't make 77 | // too much sense to protect against such simple ones, as the user can 78 | // always build recursions by other means. 79 | // We'd have to count the recursion in the context somehow and eventually 80 | // stop. 81 | let context = RuleContext(ruleModel: [ 82 | \.title <= \.title 83 | ]) 84 | _ = context.title 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Tests/SwiftUITests/TestEnvironmentKeys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestEnvironmentKeys.swift 3 | // SwiftUITests 4 | // 5 | // Created by Helge Heß on 30.08.19. 6 | // 7 | 8 | #if canImport(UIKit) 9 | import class UIKit.UIColor 10 | typealias UXColor = UIColor 11 | #elseif canImport(AppKit) 12 | import class AppKit.NSColor 13 | typealias UXColor = NSColor 14 | #endif 15 | 16 | import SwiftUIRules 17 | 18 | struct Todo { 19 | enum Priority { 20 | case low, normal, high 21 | } 22 | 23 | var title : String 24 | var completed : Bool 25 | var priority : Priority 26 | } 27 | 28 | struct VerbEnvironmentKey: DynamicEnvironmentKey { 29 | public static let defaultValue = "view" 30 | } 31 | 32 | struct TitleEnvironmentKey: DynamicEnvironmentKey { 33 | public static let defaultValue = "Hello World" 34 | } 35 | 36 | struct NavigationBarTitleEnvironmentKey: DynamicEnvironmentKey { 37 | public static let defaultValue = TitleEnvironmentKey.defaultValue 38 | } 39 | 40 | struct ColorEnvironmentKey: DynamicEnvironmentKey { 41 | public static let defaultValue = UXColor.black 42 | } 43 | 44 | struct TodoEnvironmentKey: DynamicEnvironmentKey { 45 | public static let defaultValue = 46 | Todo(title: "Buy Beer", completed: false, priority: .high) 47 | } 48 | 49 | extension DynamicEnvironmentPathes { 50 | 51 | var verb : String { 52 | set { self[dynamic: VerbEnvironmentKey.self] = newValue } 53 | get { self[dynamic: VerbEnvironmentKey.self] } 54 | } 55 | var title : String { 56 | set { self[dynamic: TitleEnvironmentKey.self] = newValue } 57 | get { self[dynamic: TitleEnvironmentKey.self] } 58 | } 59 | var navigationBarTitle : String { 60 | set { self[dynamic: NavigationBarTitleEnvironmentKey.self] = newValue } 61 | get { self[dynamic: NavigationBarTitleEnvironmentKey.self] } 62 | } 63 | 64 | var color : UXColor { 65 | set { self[dynamic: ColorEnvironmentKey.self] = newValue } 66 | get { self[dynamic: ColorEnvironmentKey.self] } 67 | } 68 | 69 | var todo : Todo { 70 | set { self[dynamic: TodoEnvironmentKey.self] = newValue } 71 | get { self[dynamic: TodoEnvironmentKey.self] } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /xcconfig/Base.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) 2017-2019 ZeeZide GmbH, All Rights Reserved 3 | // Created by Helge Hess on 23/01/2017. 4 | // 5 | 6 | // -------------------------- Base config ----------------------------- 7 | 8 | SWIFT_VERSION = 5.1 9 | 10 | // Deployment Targets 11 | MACOSX_DEPLOYMENT_TARGET = 10.15 12 | IPHONEOS_DEPLOYMENT_TARGET = 13.0 13 | WATCHOS_DEPLOYMENT_TARGET = 6.0 14 | TVOS_DEPLOYMENT_TARGET = 13.0 15 | SUPPORTS_MACCATALYST = NO 16 | 17 | // Signing 18 | CODE_SIGN_IDENTITY = - 19 | DEVELOPMENT_TEAM = 20 | 21 | // Include 22 | ALWAYS_SEARCH_USER_PATHS = NO 23 | 24 | // Language 25 | GCC_C_LANGUAGE_STANDARD = gnu11 26 | GCC_NO_COMMON_BLOCKS = YES 27 | CLANG_ENABLE_MODULES = YES 28 | CLANG_ENABLE_OBJC_ARC = YES 29 | CLANG_ENABLE_OBJC_WEAK = YES 30 | ENABLE_STRICT_OBJC_MSGSEND = YES 31 | 32 | // Warnings 33 | CLANG_WARN_BOOL_CONVERSION = YES 34 | CLANG_WARN_CONSTANT_CONVERSION = YES 35 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR 36 | CLANG_WARN_EMPTY_BODY = YES 37 | CLANG_WARN_ENUM_CONVERSION = YES 38 | CLANG_WARN_INT_CONVERSION = YES 39 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR 40 | CLANG_WARN_UNREACHABLE_CODE = YES 41 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 42 | CLANG_WARN_INFINITE_RECURSION = YES 43 | CLANG_WARN_SUSPICIOUS_MOVE = YES 44 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES 45 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES 46 | CLANG_WARN_COMMA = YES 47 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES 48 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES 49 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES 50 | CLANG_WARN_STRICT_PROTOTYPES = YES 51 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 52 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES 53 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 54 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES 55 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR 56 | GCC_WARN_UNDECLARED_SELECTOR = YES 57 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE 58 | GCC_WARN_UNUSED_FUNCTION = YES 59 | GCC_WARN_UNUSED_VARIABLE = YES 60 | 61 | // Analyzer 62 | CLANG_ANALYZER_NONNULL = YES 63 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES 64 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE 65 | 66 | // Debug 67 | DEBUG_INFORMATION_FORMAT = dwarf 68 | DEBUG_INFORMATION_FORMAT = dwarf-with-dsym 69 | 70 | // Swift Optimization 71 | SWIFT_OPTIMIZATION_LEVEL_Release = -Owholemodule 72 | SWIFT_OPTIMIZATION_LEVEL_Debug = -Onone 73 | SWIFT_OPTIMIZATION_LEVEL = $(SWIFT_OPTIMIZATION_LEVEL_$(CONFIGURATION)) 74 | 75 | SWIFT_ACTIVE_COMPILATION_CONDITIONS_Release = 76 | SWIFT_ACTIVE_COMPILATION_CONDITIONS_Debug = DEBUG 77 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(SWIFT_ACTIVE_COMPILATION_CONDITIONS_$(CONFIGURATION)) 78 | 79 | MTL_FAST_MATH = YES 80 | --------------------------------------------------------------------------------