From f844b4357e84197c3d02ef81a59806748f210e2b Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Tue, 12 Dec 2023 13:17:41 -0800 Subject: [PATCH 1/7] Upgrade rules_apple v3.1.1 and use ios_build_test (#11) --- .bazelversion | 1 + .github/workflows/bazel.yml | 2 +- BUILD.bazel | 12 ++++++++++-- WORKSPACE.bazel | 4 ++-- 4 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 .bazelversion diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 0000000..66ce77b --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +7.0.0 diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index ea11f9e..e7a62e8 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -17,4 +17,4 @@ jobs: - name: Checkout source uses: actions/checkout@v3 - name: Build - run: bazelisk build --apple_platform_type=ios --cpu=ios_x86_64 //:CombineUI + run: bazelisk build --noenable_bzlmod //:all diff --git a/BUILD.bazel b/BUILD.bazel index ba1f232..44eefd4 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,8 +1,16 @@ +load("@build_bazel_rules_apple//apple:ios.bzl", "ios_build_test") load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") +ios_build_test( + name = "CombineUI-iOS", + minimum_os_version = "15.0", + targets = [":CombineUI"], +) + swift_library( name = "CombineUI", - module_name = "CombineUI", - srcs = glob(["Sources/CombineUI/**/*.swift"], allow_empty = False), + srcs = glob(["Sources/CombineUI/**/*.swift"]), + copts = ["-strict-concurrency=complete"], visibility = ["//visibility:public"], + tags = ["manual"], ) diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index b7013e7..4755b32 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -2,8 +2,8 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( name = "build_bazel_rules_apple", - sha256 = "8ac4c7997d863f3c4347ba996e831b5ec8f7af885ee8d4fe36f1c3c8f0092b2c", - url = "https://github.com/bazelbuild/rules_apple/releases/download/2.5.0/rules_apple.2.5.0.tar.gz", + sha256 = "34c41bfb59cdaea29ac2df5a2fa79e5add609c71bb303b2ebb10985f93fa20e7", + url = "https://github.com/bazelbuild/rules_apple/releases/download/3.1.1/rules_apple.3.1.1.tar.gz", ) load( From 6d7c36849ae0766b5e50159d128f062af0b320aa Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Fri, 15 Dec 2023 09:58:14 -0800 Subject: [PATCH 2/7] Apply standard Swift package configuration (#12) --- .github/workflows/artifactory.yml | 2 +- .github/workflows/bazel.yml | 2 +- .github/workflows/swift.yml | 4 ++-- .swiftlint-rules.yml | 5 ++++- Package.resolved | 8 ++++---- Package.swift | 8 ++++---- Plugins/SwiftLintPlugin/SwiftLintPlugin.swift | 9 +++++++-- 7 files changed, 23 insertions(+), 15 deletions(-) diff --git a/.github/workflows/artifactory.yml b/.github/workflows/artifactory.yml index a5a5d05..5fc2309 100644 --- a/.github/workflows/artifactory.yml +++ b/.github/workflows/artifactory.yml @@ -5,7 +5,7 @@ on: tags: ['[0-9]+.[0-9]+.[0-9]+'] env: - DEVELOPER_DIR: /Applications/Xcode_14.3.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_15.0.app/Contents/Developer jobs: artifactory: diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index e7a62e8..1e35ffa 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -7,7 +7,7 @@ on: branches: [ main ] env: - DEVELOPER_DIR: /Applications/Xcode_14.3.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_15.0.app/Contents/Developer jobs: bazel: diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index c2e24a7..bd84708 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -7,7 +7,7 @@ on: branches: [ main ] env: - DEVELOPER_DIR: /Applications/Xcode_14.3.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_15.0.app/Contents/Developer jobs: swift: @@ -29,5 +29,5 @@ jobs: run: xcodebuild -resolvePackageDependencies - name: Build run: xcodebuild build-for-testing -scheme "CombineUI" -destination "name=$SIMULATOR,OS=latest" - - name: Run tests + - name: Test run: xcodebuild test-without-building -scheme "CombineUI" -destination "name=$SIMULATOR,OS=latest" diff --git a/.swiftlint-rules.yml b/.swiftlint-rules.yml index 971e09d..8695ec8 100644 --- a/.swiftlint-rules.yml +++ b/.swiftlint-rules.yml @@ -11,7 +11,7 @@ only_rules: - anonymous_argument_in_multiline_closure - anyobject_protocol - array_init -- attributes +# - attributes - balanced_xctest_lifecycle - blanket_disable_command - block_based_kvo @@ -129,6 +129,7 @@ only_rules: - no_grouping_extension # - no_magic_numbers - no_space_in_method_call +- non_overridable_class_declaration - notification_center_detachment - ns_number_init_as_function_reference - nslocalizedstring_key @@ -154,6 +155,7 @@ only_rules: - private_outlet - private_over_fileprivate - private_subject +- private_swiftui_state - private_unit_test - prohibited_interface_builder - prohibited_super_call @@ -209,6 +211,7 @@ only_rules: - unavailable_function - unhandled_throwing_task - unneeded_break_in_switch +# - unneeded_override - unneeded_parentheses_in_closure_argument - unneeded_synthesized_initializer - unowned_variable_capture diff --git a/Package.resolved b/Package.resolved index ba13580..4482d29 100644 --- a/Package.resolved +++ b/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/mattgallagher/CwlPreconditionTesting.git", "state" : { - "revision" : "a23ded2c91df9156628a6996ab4f347526f17b6b", - "version" : "2.1.2" + "revision" : "dc9af4781f2afdd1e68e90f80b8603be73ea7abc", + "version" : "2.2.0" } }, { @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Quick/Nimble.git", "state" : { - "revision" : "f552a16f434eef1f18b62985172489f41d37a18e", - "version" : "12.2.0" + "revision" : "d616f15123bfb36db1b1075153f73cf40605b39d", + "version" : "13.0.0" } } ], diff --git a/Package.swift b/Package.swift index f02bed4..05a4681 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.8 +// swift-tools-version:5.9 import PackageDescription @@ -22,7 +22,7 @@ let package = Package( dependencies: [ .package( url: "https://github.com/Quick/Nimble.git", - from: "12.0.0"), + from: "13.0.0"), ], targets: [ .target( @@ -51,7 +51,7 @@ let package = Package( path: "Plugins/SwiftLintPlugin"), .binaryTarget( name: SwiftLint.binary, - url: "https://github.com/realm/SwiftLint/releases/download/0.52.4/SwiftLintBinary-macos.artifactbundle.zip", - checksum: "8a8095e6235a07d00f34a9e500e7568b359f6f66a249f36d12cd846017a8c6f5"), + url: "https://github.com/realm/SwiftLint/releases/download/0.54.0/SwiftLintBinary-macos.artifactbundle.zip", + checksum: "963121d6babf2bf5fd66a21ac9297e86d855cbc9d28322790646b88dceca00f1"), ] ) diff --git a/Plugins/SwiftLintPlugin/SwiftLintPlugin.swift b/Plugins/SwiftLintPlugin/SwiftLintPlugin.swift index 46e744e..9befef7 100644 --- a/Plugins/SwiftLintPlugin/SwiftLintPlugin.swift +++ b/Plugins/SwiftLintPlugin/SwiftLintPlugin.swift @@ -62,7 +62,9 @@ internal struct SwiftLintPlugin: BuildToolPlugin { target: Target ) throws -> [String: String] { - let workingDirectory: Path = try target.directory.resolveWorkingDirectory(in: context.package.directory) + let workingDirectory: Path = try target + .directory + .resolveWorkingDirectory(in: context.package.directory) return ["BUILD_WORKSPACE_DIRECTORY": "\(workingDirectory)"] } @@ -76,9 +78,12 @@ internal struct SwiftLintPlugin: BuildToolPlugin { print("Environment:", environment) + guard !swiftFiles.isEmpty + else { return [] } + let arguments: [String] = ["lint", "--quiet", "--force-exclude", "--cache-path", "\(path)"] - return swiftFiles.isEmpty ? [] : [ + return [ .prebuildCommand( displayName: "SwiftLint", executable: executable.path, From a9db6711be990d896540778b58f702e437952e1c Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Sun, 7 Jan 2024 16:55:31 -0800 Subject: [PATCH 3/7] Add default control state argument (#13) --- Sources/CombineUI/UIKit/Bindings/Bindable+UIButton.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UIButton.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UIButton.swift index bd81dca..93e0945 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UIButton.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UIButton.swift @@ -9,13 +9,13 @@ extension Bindable where Target: UIButton { @preconcurrency @MainActor - public func title(for state: UIControl.State) -> Binding { + public func title(for state: UIControl.State = .normal) -> Binding { Binding(self) { $0.setTitle($1, for: state) } } @preconcurrency @MainActor - public func attributedTitle(for state: UIControl.State) -> Binding { + public func attributedTitle(for state: UIControl.State = .normal) -> Binding { Binding(self) { $0.setAttributedTitle(NSAttributedString($1), for: state) } } } From ddba01356d24333fa7c2e7da81472c45c7afe09a Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Sun, 7 Jan 2024 16:55:42 -0800 Subject: [PATCH 4/7] Format attributes on their own lines (#14) --- .../UIKit/Bindings/Bindable+UIControl.swift | 4 +++- .../UIKit/Bindings/Bindable+UIDatePicker.swift | 8 ++++++-- .../Bindings/Bindable+UIGestureRecognizer.swift | 4 +++- .../CombineUI/UIKit/Bindings/Bindable+UILabel.swift | 12 +++++++++--- .../UIKit/Bindings/Bindable+UIPageControl.swift | 4 +++- .../UIKit/Bindings/Bindable+UIProgressView.swift | 4 +++- .../UIKit/Bindings/Bindable+UIRefreshControl.swift | 4 +++- .../UIKit/Bindings/Bindable+UISegmentedControl.swift | 4 +++- .../CombineUI/UIKit/Bindings/Bindable+UISlider.swift | 4 +++- .../UIKit/Bindings/Bindable+UIStepper.swift | 4 +++- .../UIKit/Bindings/Bindable+UISwiftch.swift | 4 +++- .../UIKit/Bindings/Bindable+UITextField.swift | 8 ++++++-- .../CombineUI/UIKit/Bindings/Bindable+UIView.swift | 8 ++++++-- .../ViewController/ViewControllerSubscription.swift | 4 +++- 14 files changed, 57 insertions(+), 19 deletions(-) diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UIControl.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UIControl.swift index 97aa286..5489623 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UIControl.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UIControl.swift @@ -7,7 +7,9 @@ import UIKit extension Bindable where Target: UIControl { - @preconcurrency @MainActor public var isEnabled: Binding { + @preconcurrency + @MainActor + public var isEnabled: Binding { Binding(self, for: \.isEnabled) } } diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UIDatePicker.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UIDatePicker.swift index b874495..743343f 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UIDatePicker.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UIDatePicker.swift @@ -7,11 +7,15 @@ import UIKit extension Bindable where Target: UIDatePicker { - @preconcurrency @MainActor public var countDownDuration: Binding { + @preconcurrency + @MainActor + public var countDownDuration: Binding { Binding(self, for: \.countDownDuration) } - @preconcurrency @MainActor public var date: Binding { + @preconcurrency + @MainActor + public var date: Binding { Binding(self, for: \.date) } diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UIGestureRecognizer.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UIGestureRecognizer.swift index cec668a..ac1eb52 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UIGestureRecognizer.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UIGestureRecognizer.swift @@ -7,7 +7,9 @@ import UIKit extension Bindable where Target: UIGestureRecognizer { - @preconcurrency @MainActor public var isEnabled: Binding { + @preconcurrency + @MainActor + public var isEnabled: Binding { Binding(self, for: \.isEnabled) } } diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UILabel.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UILabel.swift index 0a5d2fe..2eaa8c8 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UILabel.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UILabel.swift @@ -7,15 +7,21 @@ import UIKit extension Bindable where Target: UILabel { - @preconcurrency @MainActor public var isEnabled: Binding { + @preconcurrency + @MainActor + public var isEnabled: Binding { Binding(self, for: \.isEnabled) } - @preconcurrency @MainActor public var text: Binding { + @preconcurrency + @MainActor + public var text: Binding { Binding(self, for: \.text) } - @preconcurrency @MainActor public var attributedText: Binding { + @preconcurrency + @MainActor + public var attributedText: Binding { Binding(self) { $0.attributedText = NSAttributedString($1) } } } diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UIPageControl.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UIPageControl.swift index 0f85927..cadeba9 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UIPageControl.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UIPageControl.swift @@ -7,7 +7,9 @@ import UIKit extension Bindable where Target: UIPageControl { - @preconcurrency @MainActor public var currentPage: Binding { + @preconcurrency + @MainActor + public var currentPage: Binding { Binding(self, for: \.currentPage) } } diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UIProgressView.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UIProgressView.swift index 9c70f7e..cfb694e 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UIProgressView.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UIProgressView.swift @@ -7,7 +7,9 @@ import UIKit extension Bindable where Target: UIProgressView { - @preconcurrency @MainActor public var progress: Binding { + @preconcurrency + @MainActor + public var progress: Binding { Binding(self, for: \.progress) } diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UIRefreshControl.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UIRefreshControl.swift index f461c3e..a9e0176 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UIRefreshControl.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UIRefreshControl.swift @@ -7,7 +7,9 @@ import UIKit extension Bindable where Target: UIRefreshControl { - @preconcurrency @MainActor public var isRefreshing: Binding { + @preconcurrency + @MainActor + public var isRefreshing: Binding { Binding(self) { refreshControl, isRefreshing in if isRefreshing { refreshControl.beginRefreshing() diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UISegmentedControl.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UISegmentedControl.swift index 650218d..4c7fd00 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UISegmentedControl.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UISegmentedControl.swift @@ -7,7 +7,9 @@ import UIKit extension Bindable where Target: UISegmentedControl { - @preconcurrency @MainActor public var selectedSegmentIndex: Binding { + @preconcurrency + @MainActor + public var selectedSegmentIndex: Binding { Binding(self, for: \.selectedSegmentIndex) } diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UISlider.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UISlider.swift index 0a177d3..112a794 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UISlider.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UISlider.swift @@ -7,7 +7,9 @@ import UIKit extension Bindable where Target: UISlider { - @preconcurrency @MainActor public var value: Binding { + @preconcurrency + @MainActor + public var value: Binding { Binding(self, for: \.value) } diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UIStepper.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UIStepper.swift index 75a20e1..9899f9c 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UIStepper.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UIStepper.swift @@ -7,7 +7,9 @@ import UIKit extension Bindable where Target: UIStepper { - @preconcurrency @MainActor public var value: Binding { + @preconcurrency + @MainActor + public var value: Binding { Binding(self, for: \.value) } } diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UISwiftch.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UISwiftch.swift index f37b077..bb9941e 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UISwiftch.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UISwiftch.swift @@ -7,7 +7,9 @@ import UIKit extension Bindable where Target: UISwitch { - @preconcurrency @MainActor public var isOn: Binding { + @preconcurrency + @MainActor + public var isOn: Binding { Binding(self, for: \.isOn) } diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UITextField.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UITextField.swift index 304e86d..dcfb1b0 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UITextField.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UITextField.swift @@ -7,11 +7,15 @@ import UIKit extension Bindable where Target: UITextField { - @preconcurrency @MainActor public var text: Binding { + @preconcurrency + @MainActor + public var text: Binding { Binding(self, for: \.text) } - @preconcurrency @MainActor public var attributedText: Binding { + @preconcurrency + @MainActor + public var attributedText: Binding { Binding(self) { $0.attributedText = NSAttributedString($1) } } } diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UIView.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UIView.swift index e342210..58da890 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UIView.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UIView.swift @@ -7,11 +7,15 @@ import UIKit extension Bindable where Target: UIView { - @preconcurrency @MainActor public var alpha: Binding { + @preconcurrency + @MainActor + public var alpha: Binding { Binding(self, for: \.alpha) } - @preconcurrency @MainActor public var isHidden: Binding { + @preconcurrency + @MainActor + public var isHidden: Binding { Binding(self, for: \.isHidden) } } diff --git a/Sources/CombineUI/UIKit/ViewController/ViewControllerSubscription.swift b/Sources/CombineUI/UIKit/ViewController/ViewControllerSubscription.swift index 098822a..6dd6a45 100644 --- a/Sources/CombineUI/UIKit/ViewController/ViewControllerSubscription.swift +++ b/Sources/CombineUI/UIKit/ViewController/ViewControllerSubscription.swift @@ -17,7 +17,9 @@ internal final class ViewControllerSubscription private weak var viewController: T? - @preconcurrency @MainActor private lazy var lifecycleViewController: LifecycleViewController = { + @preconcurrency + @MainActor + private lazy var lifecycleViewController: LifecycleViewController = { let lifecycleViewController: LifecycleViewController = .init() lifecycleViewController.delegate = self return lifecycleViewController From 58744450770c7673772c4058fa60ea285b7addc6 Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Mon, 8 Jan 2024 08:57:35 -0800 Subject: [PATCH 5/7] Improve readme (removing print statement) (#15) * Remove extraneous comment * Remove print statement --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cb2dcb8..37a1f9c 100644 --- a/README.md +++ b/README.md @@ -563,7 +563,7 @@ Just(true) control .publisher(for: .primaryActionTriggered) - .sink { print($0.contains(.primaryActionTriggered)) } // true + .sink { controlEvents in } .store(in: &cancellables) ``` From d04ee3d51309996eed08b2af6f758e9974c56416 Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Sat, 13 Jan 2024 09:11:01 -0800 Subject: [PATCH 6/7] Add additional bindings (#16) --- README.md | 633 +++++++++++++++++- .../UIKit/Bindings/Bindable+UIButton.swift | 24 + .../UIKit/Bindings/Bindable+UIImageView.swift | 27 + .../UIKit/Bindings/Bindable+UILabel.swift | 12 + .../Bindings/Bindable+UIPageControl.swift | 24 + .../Bindings/Bindable+UIProgressView.swift | 12 + .../Bindings/Bindable+UIRefreshControl.swift | 12 + .../Bindable+UISegmentedControl.swift | 24 + .../UIKit/Bindings/Bindable+UISlider.swift | 36 + .../UIKit/Bindings/Bindable+UIStepper.swift | 36 + .../UIKit/Bindings/Bindable+UISwiftch.swift | 12 + .../UIKit/Bindings/Bindable+UITextField.swift | 30 + .../UIKit/Bindings/Bindable+UITextView.swift | 45 ++ .../UIKit/Bindings/Bindable+UIView.swift | 54 ++ .../UIKit/BindingsTests/UIButtonTests.swift | 30 + .../BindingsTests/UIImageViewTests.swift | 31 + .../UIKit/BindingsTests/UILabelTests.swift | 15 + .../BindingsTests/UIPageControlTests.swift | 32 +- .../BindingsTests/UIProgressViewTests.swift | 16 + .../BindingsTests/UIRefreshControlTests.swift | 15 + .../UISegmentedControlTests.swift | 28 + .../UIKit/BindingsTests/UISliderTests.swift | 42 ++ .../UIKit/BindingsTests/UIStepperTests.swift | 44 +- .../UIKit/BindingsTests/UISwitchTests.swift | 14 + .../BindingsTests/UITextFieldTests.swift | 36 + .../UIKit/BindingsTests/UITextViewTests.swift | 53 ++ .../UIKit/BindingsTests/UIViewTests.swift | 67 ++ 27 files changed, 1371 insertions(+), 33 deletions(-) create mode 100644 Sources/CombineUI/UIKit/Bindings/Bindable+UIImageView.swift create mode 100644 Sources/CombineUI/UIKit/Bindings/Bindable+UITextView.swift create mode 100644 Tests/CombineUITests/UIKit/BindingsTests/UIImageViewTests.swift create mode 100644 Tests/CombineUITests/UIKit/BindingsTests/UITextViewTests.swift diff --git a/README.md b/README.md index 37a1f9c..a0d2a5a 100644 --- a/README.md +++ b/README.md @@ -148,15 +148,25 @@ Bindings - - - - + + + + - + + + + + + + + + + + @@ -166,6 +176,16 @@ Bindings + + + + + + + + + + @@ -195,11 +215,37 @@ Bindings - + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -211,13 +257,43 @@ Bindings - + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + @@ -228,13 +304,28 @@ Bindings - + + + + + + + + + + + - + + + + + + @@ -245,7 +336,52 @@ Bindings - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -256,13 +392,53 @@ Bindings - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + @@ -273,7 +449,32 @@ Bindings - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -284,11 +485,87 @@ Bindings - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -410,6 +687,7 @@ Extension Methods - [UIControl](#uicontrol) - [UIDatePicker](#uidatepicker) - [UIGestureRecognizer](#uigesturerecognizer) + - [UIImageView](#uiimageview) - [UILabel](#uilabel) - [UIPageControl](#uipagecontrol) - [UIProgressView](#uiprogressview) @@ -421,6 +699,7 @@ Extension Methods - [UIStepper](#uistepper) - [UISwitch](#uiswitch) - [UITextField](#uitextfield) + - [UITextView](#uitextview) - [UIView](#uiview) - [UIViewController](#uiviewcontroller) - [Caveats](#caveats) @@ -441,8 +720,12 @@ Extension Methods ### Bindings ```swift +func titleColor(for state: UIControl.State) -> Binding +func titleShadowColor(for state: UIControl.State) -> Binding func title(for state: UIControl.State) -> Binding func attributedTitle(for state: UIControl.State) -> Binding +func image(for state: UIControl.State) -> Binding +func backgroundImage(for state: UIControl.State) -> Binding ``` ### Extension Method @@ -466,6 +749,14 @@ $button // Bindings +Just(.systemPink) + .bind(to: button.bindable.titleColor(for: .normal)) + .store(in: &cancellables) + +Just(.systemPink) + .bind(to: button.bindable.titleShadowColor(for: .normal)) + .store(in: &cancellables) + Just("Title") .bind(to: button.bindable.title(for: .normal)) .store(in: &cancellables) @@ -474,6 +765,14 @@ Just(AttributedString("Title")) .bind(to: button.bindable.attributedTitle(for: .normal)) .store(in: &cancellables) +Just(.checkmark) + .bind(to: button.bindable.image(for: .normal)) + .store(in: &cancellables) + +Just(.checkmark) + .bind(to: button.bindable.backgroundImage(for: .normal)) + .store(in: &cancellables) + // Extension Method button @@ -710,12 +1009,42 @@ swipe - The gesture recognizer will be added to the provided view automatically. +## `UIImageView` + +### Bindings + +```swift +var image: Binding +var highlightedImage: Binding +var isHighlighted: Binding +``` + +### Code Example + +```swift +let imageView = UIImageView() + +Just(.checkmark) + .bind(to: imageView.bindable.image) + .store(in: &cancellables) + +Just(.checkmark) + .bind(to: imageView.bindable.highlightedImage) + .store(in: &cancellables) + +Just(true) + .bind(to: imageView.bindable.isHighlighted) + .store(in: &cancellables) +``` + ## `UILabel` ### Bindings ```swift var isEnabled: Binding +var font: Binding +var textColor: Binding var text: Binding var attributedText: Binding ``` @@ -723,19 +1052,25 @@ var attributedText: Binding ### Code Example ```swift -// Bindings - let label = UILabel() Just(true) .bind(to: label.bindable.isEnabled) .store(in: &cancellables) +Just(.preferredFont(forTextStyle: .body)) + .bind(to: label.bindable.font) + .store(in: &cancellables) + +Just(.systemPink) + .bind(to: label.bindable.textColor) + .store(in: &cancellables) + Just("Text") .bind(to: label.bindable.text) .store(in: &cancellables) -Just("Attributed Text") +Just(AttributedString("Text")) .bind(to: label.bindable.attributedText) .store(in: &cancellables) ``` @@ -752,10 +1087,14 @@ Just("Attributed Text") @PageControl // Projected Value: AnyPublisher ``` -### Binding +### Bindings ```swift +var pageIndicatorTintColor: Binding +var currentPageIndicatorTintColor: Binding var currentPage: Binding +var numberOfPages: Binding +var hidesForSinglePage: Binding ``` ### Extension Method @@ -775,12 +1114,28 @@ $pageControl .sink { currentPage in } .store(in: &cancellables) -// Binding +// Bindings + +Just(.systemPink) + .bind(to: pageControl.bindable.pageIndicatorTintColor) + .store(in: &cancellables) + +Just(.systemPink) + .bind(to: pageControl.bindable.currentPageIndicatorTintColor) + .store(in: &cancellables) Just(1) .bind(to: pageControl.bindable.currentPage) .store(in: &cancellables) +Just(1) + .bind(to: pageControl.bindable.numberOfPages) + .store(in: &cancellables) + +Just(true) + .bind(to: pageControl.bindable.hidesForSinglePage) + .store(in: &cancellables) + // Extension Method pageControl @@ -804,6 +1159,8 @@ pageControl ### Bindings ```swift +var trackTintColor: Binding +var progressTintColor: Binding var progress: Binding func progress(animated: Bool) -> Binding @@ -828,6 +1185,14 @@ $progressView // Bindings +Just(.systemPink) + .bind(to: progressView.bindable.trackTintColor) + .store(in: &cancellables) + +Just(.systemPink) + .bind(to: progressView.bindable.progressTintColor) + .store(in: &cancellables) + Just(1) .bind(to: progressView.bindable.progress) .store(in: &cancellables) @@ -871,9 +1236,11 @@ class ProgressView: UIProgressView { @RefreshControl // Projected Value: AnyPublisher ``` -### Binding +### Bindings ```swift +var tintColor: Binding +var attributedTitle: Binding var isRefreshing: Binding ``` @@ -894,7 +1261,15 @@ $refreshControl .sink { print("Refreshing") } .store(in: &cancellables) -// Binding +// Bindings + +Just(.systemPink) + .bind(to: refreshControl.bindable.tintColor) + .store(in: &cancellables) + +Just(AttributedString("Title")) + .bind(to: refreshControl.bindable.attributedTitle) + .store(in: &cancellables) Just(true) .bind(to: refreshControl.bindable.isRefreshing) @@ -940,8 +1315,6 @@ var didChangeAdjustedContentInset: AnyPublisher ### Code Example ```swift -// Property Wrapper - @ScrollView var scrollView = UIScrollView() $scrollView @@ -978,8 +1351,6 @@ var selectedScopeButtonIndexDidChange: AnyPublisher ### Code Example ```swift -// Property Wrapper - @SearchBar var searchBar = UISearchBar() $searchBar @@ -1003,9 +1374,13 @@ $searchBar ### Bindings ```swift +var isMomentary: Binding var selectedSegmentIndex: Binding func isEnabledForSegment(at index: Int) -> Binding +func widthForSegment(at index: Int) -> Binding +func titleForSegment(at index: Int) -> Binding +func imageForSegment(at index: Int) -> Binding ``` ### Extension Method @@ -1027,6 +1402,10 @@ $segmentedControl // Bindings +Just(true) + .bind(to: segmentedControl.bindable.isMomentary) + .store(in: &cancellables) + Just(1) .bind(to: segmentedControl.bindable.selectedSegmentIndex) .store(in: &cancellables) @@ -1035,6 +1414,18 @@ Just(true) .bind(to: segmentedControl.bindable.isEnabledForSegment(at: 1)) .store(in: &cancellables) +Just(100) + .bind(to: segmentedControl.bindable.widthForSegment(at: 1)) + .store(in: &cancellables) + +Just("Title") + .bind(to: segmentedControl.bindable.titleForSegment(at: 1)) + .store(in: &cancellables) + +Just(.checkmark) + .bind(to: segmentedControl.bindable.imageForSegment(at: 1)) + .store(in: &cancellables) + // Extension Method segmentedControl @@ -1058,6 +1449,12 @@ segmentedControl ### Bindings ```swift +var isContinuous: Binding +var minimumValue: Binding +var maximumValue: Binding +var minimumTrackTintColor: Binding +var maximumTrackTintColor: Binding +var thumbTintColor: Binding var value: Binding func value(animated: Bool) -> Binding @@ -1082,6 +1479,30 @@ $slider // Bindings +Just(true) + .bind(to: slider.bindable.isContinuous) + .store(in: &cancellables) + +Just(1) + .bind(to: slider.bindable.minimumValue) + .store(in: &cancellables) + +Just(100) + .bind(to: slider.bindable.maximumValue) + .store(in: &cancellables) + +Just(.systemPink) + .bind(to: slider.bindable.minimumTrackTintColor) + .store(in: &cancellables) + +Just(.systemPink) + .bind(to: slider.bindable.maximumTrackTintColor) + .store(in: &cancellables) + +Just(.systemPink) + .bind(to: slider.bindable.thumbTintColor) + .store(in: &cancellables) + Just(1) .bind(to: slider.bindable.value) .store(in: &cancellables) @@ -1110,9 +1531,15 @@ slider @Stepper // Projected Value: AnyPublisher ``` -### Binding +### Bindings ```swift +var isContinuous: Binding +var autorepeat: Binding +var wraps: Binding +var minimumValue: Binding +var maximumValue: Binding +var stepValue: Binding var value: Binding ``` @@ -1133,7 +1560,31 @@ $stepper .sink { value in } .store(in: &cancellables) -// Binding +// Bindings + +Just(true) + .bind(to: stepper.bindable.isContinuous) + .store(in: &cancellables) + +Just(true) + .bind(to: stepper.bindable.autorepeat) + .store(in: &cancellables) + +Just(true) + .bind(to: stepper.bindable.wraps) + .store(in: &cancellables) + +Just(1) + .bind(to: stepper.bindable.minimumValue) + .store(in: &cancellables) + +Just(100) + .bind(to: stepper.bindable.maximumValue) + .store(in: &cancellables) + +Just(10) + .bind(to: stepper.bindable.stepValue) + .store(in: &cancellables) Just(100) .bind(to: stepper.bindable.value) @@ -1162,6 +1613,8 @@ stepper ### Bindings ```swift +var onTintColor: Binding +var thumbTintColor: Binding var isOn: Binding func isOn(animated: Bool) -> Binding @@ -1186,6 +1639,14 @@ $switch // Bindings +Just(.systemPink) + .bind(to: `switch`.bindable.onTintColor) + .store(in: &cancellables) + +Just(.systemPink) + .bind(to: `switch`.bindable.thumbTintColor) + .store(in: &cancellables) + Just(true) .bind(to: `switch`.bindable.isOn) .store(in: &cancellables) @@ -1237,6 +1698,11 @@ var didChangeSelection: AnyPublisher ### Bindings ```swift +var font: Binding +var textColor: Binding +var textAlignment: Binding +var placeholder: Binding +var attributedPlaceholder: Binding var text: Binding var attributedText: Binding ``` @@ -1245,7 +1711,6 @@ var attributedText: Binding ```swift func textPublisher() -> AnyPublisher - func attributedTextPublisher() -> AnyPublisher ``` @@ -1273,6 +1738,26 @@ $textField // Bindings +Just(.preferredFont(forTextStyle: .body)) + .bind(to: textField.bindable.font) + .store(in: &cancellables) + +Just(.systemPink) + .bind(to: textField.bindable.textColor) + .store(in: &cancellables) + +Just(.natural) + .bind(to: textField.bindable.textAlignment) + .store(in: &cancellables) + +Just("Placeholder") + .bind(to: textField.bindable.placeholder) + .store(in: &cancellables) + +Just(AttributedString("Placeholder")) + .bind(to: textField.bindable.attributedPlaceholder) + .store(in: &cancellables) + Just("Text") .bind(to: textField.bindable.text) .store(in: &cancellables) @@ -1294,12 +1779,64 @@ textField .store(in: &cancellables) ``` +## `UITextView` + +### Bindings + +```swift +var isEditable: Binding +var font: Binding +var textColor: Binding +var textAlignment: Binding +var text: Binding +var attributedText: Binding +``` + +### Code Example + +```swift +let textView = UITextView() + +Just(true) + .bind(to: textView.bindable.isEditable) + .store(in: &cancellables) + +Just(.preferredFont(forTextStyle: .body)) + .bind(to: textView.bindable.font) + .store(in: &cancellables) + +Just(.systemPink) + .bind(to: textView.bindable.textColor) + .store(in: &cancellables) + +Just(.natural) + .bind(to: textView.bindable.textAlignment) + .store(in: &cancellables) + +Just("Text") + .bind(to: textView.bindable.text) + .store(in: &cancellables) + +Just(AttributedString("Text")) + .bind(to: textView.bindable.attributedText) + .store(in: &cancellables) +``` + ## `UIView` ### Bindings ```swift +var isUserInteractionEnabled: Binding +var isMultipleTouchEnabled: Binding +var isExclusiveTouch: Binding +var clipsToBounds: Binding +var tintColor: Binding +var backgroundColor: Binding +var borderColor: Binding +var shadowColor: Binding var alpha: Binding +var isOpaque: Binding var isHidden: Binding ``` @@ -1310,14 +1847,48 @@ Just as every CombineUI binding may be used with subclasses of its supported typ ### Code Example ```swift -// Bindings - let view = UIView() +Just(true) + .bind(to: view.bindable.isUserInteractionEnabled) + .store(in: &cancellables) + +Just(true) + .bind(to: view.bindable.isMultipleTouchEnabled) + .store(in: &cancellables) + +Just(true) + .bind(to: view.bindable.isExclusiveTouch) + .store(in: &cancellables) + +Just(true) + .bind(to: view.bindable.clipsToBounds) + .store(in: &cancellables) + +Just(.systemPink) + .bind(to: view.bindable.tintColor) + .store(in: &cancellables) + +Just(.systemPink) + .bind(to: view.bindable.backgroundColor) + .store(in: &cancellables) + +Just(.systemPink) + .bind(to: view.bindable.borderColor) + .store(in: &cancellables) + +Just(.systemPink) + .bind(to: view.bindable.shadowColor) + .store(in: &cancellables) + Just(0.5) .bind(to: view.bindable.alpha) .store(in: &cancellables) +Just(true) + .bind(to: view.bindable.isOpaque) + .store(in: &cancellables) + Just(true) .bind(to: view.bindable.isHidden) .store(in: &cancellables) diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UIButton.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UIButton.swift index 93e0945..38b5ae4 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UIButton.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UIButton.swift @@ -7,6 +7,18 @@ import UIKit extension Bindable where Target: UIButton { + @preconcurrency + @MainActor + public func titleColor(for state: UIControl.State = .normal) -> Binding { + Binding(self) { $0.setTitleColor($1, for: state) } + } + + @preconcurrency + @MainActor + public func titleShadowColor(for state: UIControl.State = .normal) -> Binding { + Binding(self) { $0.setTitleShadowColor($1, for: state) } + } + @preconcurrency @MainActor public func title(for state: UIControl.State = .normal) -> Binding { @@ -18,4 +30,16 @@ extension Bindable where Target: UIButton { public func attributedTitle(for state: UIControl.State = .normal) -> Binding { Binding(self) { $0.setAttributedTitle(NSAttributedString($1), for: state) } } + + @preconcurrency + @MainActor + public func image(for state: UIControl.State = .normal) -> Binding { + Binding(self) { $0.setImage($1, for: state) } + } + + @preconcurrency + @MainActor + public func backgroundImage(for state: UIControl.State = .normal) -> Binding { + Binding(self) { $0.setBackgroundImage($1, for: state) } + } } diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UIImageView.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UIImageView.swift new file mode 100644 index 0000000..64a0a84 --- /dev/null +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UIImageView.swift @@ -0,0 +1,27 @@ +// +// Copyright © 2024 Tinder (Match Group, LLC) +// + +import Combine +import UIKit + +extension Bindable where Target: UIImageView { + + @preconcurrency + @MainActor + public var image: Binding { + Binding(self, for: \.image) + } + + @preconcurrency + @MainActor + public var highlightedImage: Binding { + Binding(self, for: \.highlightedImage) + } + + @preconcurrency + @MainActor + public var isHighlighted: Binding { + Binding(self, for: \.isHighlighted) + } +} diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UILabel.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UILabel.swift index 2eaa8c8..1d23114 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UILabel.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UILabel.swift @@ -13,6 +13,18 @@ extension Bindable where Target: UILabel { Binding(self, for: \.isEnabled) } + @preconcurrency + @MainActor + public var font: Binding { + Binding(self, for: \.font) + } + + @preconcurrency + @MainActor + public var textColor: Binding { + Binding(self, for: \.textColor) + } + @preconcurrency @MainActor public var text: Binding { diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UIPageControl.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UIPageControl.swift index cadeba9..95a82df 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UIPageControl.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UIPageControl.swift @@ -7,9 +7,33 @@ import UIKit extension Bindable where Target: UIPageControl { + @preconcurrency + @MainActor + public var pageIndicatorTintColor: Binding { + Binding(self, for: \.pageIndicatorTintColor) + } + + @preconcurrency + @MainActor + public var currentPageIndicatorTintColor: Binding { + Binding(self, for: \.currentPageIndicatorTintColor) + } + @preconcurrency @MainActor public var currentPage: Binding { Binding(self, for: \.currentPage) } + + @preconcurrency + @MainActor + public var numberOfPages: Binding { + Binding(self, for: \.numberOfPages) + } + + @preconcurrency + @MainActor + public var hidesForSinglePage: Binding { + Binding(self, for: \.hidesForSinglePage) + } } diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UIProgressView.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UIProgressView.swift index cfb694e..1f308e2 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UIProgressView.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UIProgressView.swift @@ -7,6 +7,18 @@ import UIKit extension Bindable where Target: UIProgressView { + @preconcurrency + @MainActor + public var trackTintColor: Binding { + Binding(self, for: \.trackTintColor) + } + + @preconcurrency + @MainActor + public var progressTintColor: Binding { + Binding(self, for: \.progressTintColor) + } + @preconcurrency @MainActor public var progress: Binding { diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UIRefreshControl.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UIRefreshControl.swift index a9e0176..4b59b68 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UIRefreshControl.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UIRefreshControl.swift @@ -7,6 +7,18 @@ import UIKit extension Bindable where Target: UIRefreshControl { + @preconcurrency + @MainActor + public var tintColor: Binding { + Binding(self, for: \.tintColor) + } + + @preconcurrency + @MainActor + public var attributedTitle: Binding { + Binding(self) { $0.attributedTitle = NSAttributedString($1) } + } + @preconcurrency @MainActor public var isRefreshing: Binding { diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UISegmentedControl.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UISegmentedControl.swift index 4c7fd00..31269df 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UISegmentedControl.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UISegmentedControl.swift @@ -7,6 +7,12 @@ import UIKit extension Bindable where Target: UISegmentedControl { + @preconcurrency + @MainActor + public var isMomentary: Binding { + Binding(self, for: \.isMomentary) + } + @preconcurrency @MainActor public var selectedSegmentIndex: Binding { @@ -18,4 +24,22 @@ extension Bindable where Target: UISegmentedControl { public func isEnabledForSegment(at index: Int) -> Binding { Binding(self) { $0.setEnabled($1, forSegmentAt: index) } } + + @preconcurrency + @MainActor + public func widthForSegment(at index: Int) -> Binding { + Binding(self) { $0.setWidth($1, forSegmentAt: index) } + } + + @preconcurrency + @MainActor + public func titleForSegment(at index: Int) -> Binding { + Binding(self) { $0.setTitle($1, forSegmentAt: index) } + } + + @preconcurrency + @MainActor + public func imageForSegment(at index: Int) -> Binding { + Binding(self) { $0.setImage($1, forSegmentAt: index) } + } } diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UISlider.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UISlider.swift index 112a794..3e00544 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UISlider.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UISlider.swift @@ -7,6 +7,42 @@ import UIKit extension Bindable where Target: UISlider { + @preconcurrency + @MainActor + public var isContinuous: Binding { + Binding(self, for: \.isContinuous) + } + + @preconcurrency + @MainActor + public var minimumValue: Binding { + Binding(self, for: \.minimumValue) + } + + @preconcurrency + @MainActor + public var maximumValue: Binding { + Binding(self, for: \.maximumValue) + } + + @preconcurrency + @MainActor + public var minimumTrackTintColor: Binding { + Binding(self, for: \.minimumTrackTintColor) + } + + @preconcurrency + @MainActor + public var maximumTrackTintColor: Binding { + Binding(self, for: \.maximumTrackTintColor) + } + + @preconcurrency + @MainActor + public var thumbTintColor: Binding { + Binding(self, for: \.thumbTintColor) + } + @preconcurrency @MainActor public var value: Binding { diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UIStepper.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UIStepper.swift index 9899f9c..da6cdbd 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UIStepper.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UIStepper.swift @@ -7,6 +7,42 @@ import UIKit extension Bindable where Target: UIStepper { + @preconcurrency + @MainActor + public var isContinuous: Binding { + Binding(self, for: \.isContinuous) + } + + @preconcurrency + @MainActor + public var autorepeat: Binding { + Binding(self, for: \.autorepeat) + } + + @preconcurrency + @MainActor + public var wraps: Binding { + Binding(self, for: \.wraps) + } + + @preconcurrency + @MainActor + public var minimumValue: Binding { + Binding(self, for: \.minimumValue) + } + + @preconcurrency + @MainActor + public var maximumValue: Binding { + Binding(self, for: \.maximumValue) + } + + @preconcurrency + @MainActor + public var stepValue: Binding { + Binding(self, for: \.stepValue) + } + @preconcurrency @MainActor public var value: Binding { diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UISwiftch.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UISwiftch.swift index bb9941e..bdfa727 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UISwiftch.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UISwiftch.swift @@ -7,6 +7,18 @@ import UIKit extension Bindable where Target: UISwitch { + @preconcurrency + @MainActor + public var onTintColor: Binding { + Binding(self, for: \.onTintColor) + } + + @preconcurrency + @MainActor + public var thumbTintColor: Binding { + Binding(self, for: \.thumbTintColor) + } + @preconcurrency @MainActor public var isOn: Binding { diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UITextField.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UITextField.swift index dcfb1b0..058063b 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UITextField.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UITextField.swift @@ -7,6 +7,36 @@ import UIKit extension Bindable where Target: UITextField { + @preconcurrency + @MainActor + public var font: Binding { + Binding(self, for: \.font) + } + + @preconcurrency + @MainActor + public var textColor: Binding { + Binding(self, for: \.textColor) + } + + @preconcurrency + @MainActor + public var textAlignment: Binding { + Binding(self, for: \.textAlignment) + } + + @preconcurrency + @MainActor + public var placeholder: Binding { + Binding(self, for: \.placeholder) + } + + @preconcurrency + @MainActor + public var attributedPlaceholder: Binding { + Binding(self) { $0.attributedPlaceholder = NSAttributedString($1) } + } + @preconcurrency @MainActor public var text: Binding { diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UITextView.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UITextView.swift new file mode 100644 index 0000000..a0f226a --- /dev/null +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UITextView.swift @@ -0,0 +1,45 @@ +// +// Copyright © 2024 Tinder (Match Group, LLC) +// + +import Combine +import UIKit + +extension Bindable where Target: UITextView { + + @preconcurrency + @MainActor + public var isEditable: Binding { + Binding(self, for: \.isEditable) + } + + @preconcurrency + @MainActor + public var font: Binding { + Binding(self, for: \.font) + } + + @preconcurrency + @MainActor + public var textColor: Binding { + Binding(self, for: \.textColor) + } + + @preconcurrency + @MainActor + public var textAlignment: Binding { + Binding(self, for: \.textAlignment) + } + + @preconcurrency + @MainActor + public var text: Binding { + Binding(self, for: \.text) + } + + @preconcurrency + @MainActor + public var attributedText: Binding { + Binding(self) { $0.attributedText = NSAttributedString($1) } + } +} diff --git a/Sources/CombineUI/UIKit/Bindings/Bindable+UIView.swift b/Sources/CombineUI/UIKit/Bindings/Bindable+UIView.swift index 58da890..a355d2e 100644 --- a/Sources/CombineUI/UIKit/Bindings/Bindable+UIView.swift +++ b/Sources/CombineUI/UIKit/Bindings/Bindable+UIView.swift @@ -7,12 +7,66 @@ import UIKit extension Bindable where Target: UIView { + @preconcurrency + @MainActor + public var isUserInteractionEnabled: Binding { + Binding(self, for: \.isUserInteractionEnabled) + } + + @preconcurrency + @MainActor + public var isMultipleTouchEnabled: Binding { + Binding(self, for: \.isMultipleTouchEnabled) + } + + @preconcurrency + @MainActor + public var isExclusiveTouch: Binding { + Binding(self, for: \.isExclusiveTouch) + } + + @preconcurrency + @MainActor + public var clipsToBounds: Binding { + Binding(self, for: \.clipsToBounds) + } + + @preconcurrency + @MainActor + public var tintColor: Binding { + Binding(self, for: \.tintColor) + } + + @preconcurrency + @MainActor + public var backgroundColor: Binding { + Binding(self, for: \.backgroundColor) + } + + @preconcurrency + @MainActor + public var borderColor: Binding { + Binding(self) { $0.layer.borderColor = $1.cgColor } + } + + @preconcurrency + @MainActor + public var shadowColor: Binding { + Binding(self) { $0.layer.shadowColor = $1.cgColor } + } + @preconcurrency @MainActor public var alpha: Binding { Binding(self, for: \.alpha) } + @preconcurrency + @MainActor + public var isOpaque: Binding { + Binding(self, for: \.isOpaque) + } + @preconcurrency @MainActor public var isHidden: Binding { diff --git a/Tests/CombineUITests/UIKit/BindingsTests/UIButtonTests.swift b/Tests/CombineUITests/UIKit/BindingsTests/UIButtonTests.swift index a306d72..83265fa 100644 --- a/Tests/CombineUITests/UIKit/BindingsTests/UIButtonTests.swift +++ b/Tests/CombineUITests/UIKit/BindingsTests/UIButtonTests.swift @@ -8,6 +8,22 @@ import XCTest final class UIButtonTests: XCTestCase { + func testTitleColor() { + let button: UIButton = .init() + button.setTitleColor(.systemMint, for: .normal) + expect(button.titleColor(for: .normal)) == .systemMint + button.bindable.titleColor(for: .normal).receiveValue(.systemPink) + expect(button.titleColor(for: .normal)) == .systemPink + } + + func testTitleShadowColor() { + let button: UIButton = .init() + button.setTitleShadowColor(.systemMint, for: .normal) + expect(button.titleShadowColor(for: .normal)) == .systemMint + button.bindable.titleShadowColor(for: .normal).receiveValue(.systemPink) + expect(button.titleShadowColor(for: .normal)) == .systemPink + } + func testTitle() { let button: UIButton = .init() expect(button.title(for: .normal)) == nil @@ -21,4 +37,18 @@ final class UIButtonTests: XCTestCase { button.bindable.attributedTitle(for: .normal).receiveValue(AttributedString("Attributed Title")) expect(button.attributedTitle(for: .normal)?.string) == "Attributed Title" } + + func testImage() { + let button: UIButton = .init() + expect(button.image(for: .normal)) == nil + button.bindable.image(for: .normal).receiveValue(.checkmark) + expect(button.image(for: .normal)) == .checkmark + } + + func testBackgroundImage() { + let button: UIButton = .init() + expect(button.backgroundImage(for: .normal)) == nil + button.bindable.backgroundImage(for: .normal).receiveValue(.checkmark) + expect(button.backgroundImage(for: .normal)) == .checkmark + } } diff --git a/Tests/CombineUITests/UIKit/BindingsTests/UIImageViewTests.swift b/Tests/CombineUITests/UIKit/BindingsTests/UIImageViewTests.swift new file mode 100644 index 0000000..cfc23cd --- /dev/null +++ b/Tests/CombineUITests/UIKit/BindingsTests/UIImageViewTests.swift @@ -0,0 +1,31 @@ +// +// Copyright © 2024 Tinder (Match Group, LLC) +// + +@testable import CombineUI +import Nimble +import XCTest + +final class UIImageViewTests: XCTestCase { + + func testImage() { + let imageView: UIImageView = .init() + expect(imageView.image) == nil + imageView.bindable.image.receiveValue(.checkmark) + expect(imageView.image) == .checkmark + } + + func testHighlightedImage() { + let imageView: UIImageView = .init() + expect(imageView.highlightedImage) == nil + imageView.bindable.highlightedImage.receiveValue(.checkmark) + expect(imageView.highlightedImage) == .checkmark + } + + func testIsHighlighted() { + let imageView: UIImageView = .init() + expect(imageView.isHighlighted) == false + imageView.bindable.isHighlighted.receiveValue(true) + expect(imageView.isHighlighted) == true + } +} diff --git a/Tests/CombineUITests/UIKit/BindingsTests/UILabelTests.swift b/Tests/CombineUITests/UIKit/BindingsTests/UILabelTests.swift index 0970108..1979db7 100644 --- a/Tests/CombineUITests/UIKit/BindingsTests/UILabelTests.swift +++ b/Tests/CombineUITests/UIKit/BindingsTests/UILabelTests.swift @@ -15,6 +15,21 @@ final class UILabelTests: XCTestCase { expect(label.isEnabled) == false } + func testFont() { + let label: UILabel = .init() + expect(label.font) == .systemFont(ofSize: 17) + label.bindable.font.receiveValue(.systemFont(ofSize: 23)) + expect(label.font) == .systemFont(ofSize: 23) + } + + func testTextColor() { + let label: UILabel = .init() + label.textColor = .systemMint + expect(label.textColor) == .systemMint + label.bindable.textColor.receiveValue(.systemPink) + expect(label.textColor) == .systemPink + } + func testText() { let label: UILabel = .init() expect(label.text) == nil diff --git a/Tests/CombineUITests/UIKit/BindingsTests/UIPageControlTests.swift b/Tests/CombineUITests/UIKit/BindingsTests/UIPageControlTests.swift index 2843175..3508c77 100644 --- a/Tests/CombineUITests/UIKit/BindingsTests/UIPageControlTests.swift +++ b/Tests/CombineUITests/UIKit/BindingsTests/UIPageControlTests.swift @@ -8,11 +8,41 @@ import XCTest final class UIPageControlTests: XCTestCase { - func testPageControl() { + func testPageIndicatorTintColor() { + let pageControl: UIPageControl = .init() + pageControl.pageIndicatorTintColor = .systemMint + expect(pageControl.pageIndicatorTintColor) == .systemMint + pageControl.bindable.pageIndicatorTintColor.receiveValue(.systemPink) + expect(pageControl.pageIndicatorTintColor) == .systemPink + } + + func testCurrentPageIndicatorTintColor() { + let pageControl: UIPageControl = .init() + pageControl.currentPageIndicatorTintColor = .systemMint + expect(pageControl.currentPageIndicatorTintColor) == .systemMint + pageControl.bindable.currentPageIndicatorTintColor.receiveValue(.systemPink) + expect(pageControl.currentPageIndicatorTintColor) == .systemPink + } + + func testCurrentPage() { let pageControl: UIPageControl = .init() pageControl.numberOfPages = 2 expect(pageControl.currentPage) == 0 pageControl.bindable.currentPage.receiveValue(1) expect(pageControl.currentPage) == 1 } + + func testNumberOfPages() { + let pageControl: UIPageControl = .init() + expect(pageControl.numberOfPages) == 0 + pageControl.bindable.numberOfPages.receiveValue(2) + expect(pageControl.numberOfPages) == 2 + } + + func testHidesForSinglePage() { + let pageControl: UIPageControl = .init() + expect(pageControl.hidesForSinglePage) == false + pageControl.bindable.hidesForSinglePage.receiveValue(true) + expect(pageControl.hidesForSinglePage) == true + } } diff --git a/Tests/CombineUITests/UIKit/BindingsTests/UIProgressViewTests.swift b/Tests/CombineUITests/UIKit/BindingsTests/UIProgressViewTests.swift index 2c33210..39db02f 100644 --- a/Tests/CombineUITests/UIKit/BindingsTests/UIProgressViewTests.swift +++ b/Tests/CombineUITests/UIKit/BindingsTests/UIProgressViewTests.swift @@ -8,6 +8,22 @@ import XCTest final class UIProgressViewTests: XCTestCase { + func testTrackTintColor() { + let progressView: UIProgressView = .init() + progressView.trackTintColor = .systemMint + expect(progressView.trackTintColor) == .systemMint + progressView.bindable.trackTintColor.receiveValue(.systemPink) + expect(progressView.trackTintColor) == .systemPink + } + + func testProgressTintColor() { + let progressView: UIProgressView = .init() + progressView.progressTintColor = .systemMint + expect(progressView.progressTintColor) == .systemMint + progressView.bindable.progressTintColor.receiveValue(.systemPink) + expect(progressView.progressTintColor) == .systemPink + } + func testProgress() { let progressView: UIProgressView = .init() expect(progressView.progress) == 0 diff --git a/Tests/CombineUITests/UIKit/BindingsTests/UIRefreshControlTests.swift b/Tests/CombineUITests/UIKit/BindingsTests/UIRefreshControlTests.swift index c414823..038f75b 100644 --- a/Tests/CombineUITests/UIKit/BindingsTests/UIRefreshControlTests.swift +++ b/Tests/CombineUITests/UIKit/BindingsTests/UIRefreshControlTests.swift @@ -26,6 +26,21 @@ final class UIRefreshControlTests: XCTestCase { } } + func testTintColor() { + let refreshControl: TestRefreshControl = .init() + refreshControl.tintColor = .systemMint + expect(refreshControl.tintColor) == .systemMint + refreshControl.bindable.tintColor.receiveValue(.systemPink) + expect(refreshControl.tintColor) == .systemPink + } + + func testAttributedTitle() { + let refreshControl: TestRefreshControl = .init() + expect(refreshControl.attributedTitle) == nil + refreshControl.bindable.attributedTitle.receiveValue(AttributedString("Attributed Text")) + expect(refreshControl.attributedTitle?.string) == "Attributed Text" + } + func testRefreshControl() { let refreshControl: TestRefreshControl = .init() expect(refreshControl.isRefreshing) == false diff --git a/Tests/CombineUITests/UIKit/BindingsTests/UISegmentedControlTests.swift b/Tests/CombineUITests/UIKit/BindingsTests/UISegmentedControlTests.swift index f27d5b0..416caa8 100644 --- a/Tests/CombineUITests/UIKit/BindingsTests/UISegmentedControlTests.swift +++ b/Tests/CombineUITests/UIKit/BindingsTests/UISegmentedControlTests.swift @@ -8,6 +8,13 @@ import XCTest final class UISegmentedControlTests: XCTestCase { + func testIsMomentary() { + let segmentedControl: UISegmentedControl = .init(items: ["item"]) + expect(segmentedControl.isMomentary) == false + segmentedControl.bindable.isMomentary.receiveValue(true) + expect(segmentedControl.isMomentary) == true + } + func testSelectedSegmentIndex() { let segmentedControl: UISegmentedControl = .init(items: ["item"]) expect(segmentedControl.selectedSegmentIndex) == -1 @@ -21,4 +28,25 @@ final class UISegmentedControlTests: XCTestCase { segmentedControl.bindable.isEnabledForSegment(at: 0).receiveValue(false) expect(segmentedControl.isEnabledForSegment(at: 0)) == false } + + func testWidthForSegmentAtIndex() { + let segmentedControl: UISegmentedControl = .init(items: ["item"]) + expect(segmentedControl.widthForSegment(at: 0)) == 0 + segmentedControl.bindable.widthForSegment(at: 0).receiveValue(10) + expect(segmentedControl.widthForSegment(at: 0)) == 10 + } + + func testTitleForSegmentAtIndex() { + let segmentedControl: UISegmentedControl = .init(items: ["item"]) + expect(segmentedControl.titleForSegment(at: 0)) == "item" + segmentedControl.bindable.titleForSegment(at: 0).receiveValue("Title") + expect(segmentedControl.titleForSegment(at: 0)) == "Title" + } + + func testImageForSegmentAtIndex() { + let segmentedControl: UISegmentedControl = .init(items: ["item"]) + expect(segmentedControl.imageForSegment(at: 0)) == nil + segmentedControl.bindable.imageForSegment(at: 0).receiveValue(.checkmark) + expect(segmentedControl.imageForSegment(at: 0)) == .checkmark + } } diff --git a/Tests/CombineUITests/UIKit/BindingsTests/UISliderTests.swift b/Tests/CombineUITests/UIKit/BindingsTests/UISliderTests.swift index 4b7548a..30920ae 100644 --- a/Tests/CombineUITests/UIKit/BindingsTests/UISliderTests.swift +++ b/Tests/CombineUITests/UIKit/BindingsTests/UISliderTests.swift @@ -8,6 +8,48 @@ import XCTest final class UISliderTests: XCTestCase { + func testIsContinuous() { + let slider: UISlider = .init() + expect(slider.isContinuous) == true + slider.bindable.isContinuous.receiveValue(false) + expect(slider.isContinuous) == false + } + + func testMinimumValue() { + let slider: UISlider = .init() + expect(slider.minimumValue) == 0 + slider.bindable.minimumValue.receiveValue(1) + expect(slider.minimumValue) == 1 + } + + func testMaximumValue() { + let slider: UISlider = .init() + expect(slider.maximumValue) == 1 + slider.bindable.maximumValue.receiveValue(10) + expect(slider.maximumValue) == 10 + } + + func testMinimumTrackTintColor() { + let slider: UISlider = .init() + expect(slider.minimumTrackTintColor) == nil + slider.bindable.minimumTrackTintColor.receiveValue(.systemPink) + expect(slider.minimumTrackTintColor) == .systemPink + } + + func testMaximumTrackTintColor() { + let slider: UISlider = .init() + expect(slider.maximumTrackTintColor) == nil + slider.bindable.maximumTrackTintColor.receiveValue(.systemPink) + expect(slider.maximumTrackTintColor) == .systemPink + } + + func testThumbTintColor() { + let slider: UISlider = .init() + expect(slider.thumbTintColor) == nil + slider.bindable.thumbTintColor.receiveValue(.systemPink) + expect(slider.thumbTintColor) == .systemPink + } + func testValue() { let slider: UISlider = .init() expect(slider.value) == 0 diff --git a/Tests/CombineUITests/UIKit/BindingsTests/UIStepperTests.swift b/Tests/CombineUITests/UIKit/BindingsTests/UIStepperTests.swift index f2e43f3..07b1588 100644 --- a/Tests/CombineUITests/UIKit/BindingsTests/UIStepperTests.swift +++ b/Tests/CombineUITests/UIKit/BindingsTests/UIStepperTests.swift @@ -8,7 +8,49 @@ import XCTest final class UIStepperTests: XCTestCase { - func testStepper() { + func testIsContinuous() { + let stepper: UIStepper = .init() + expect(stepper.isContinuous) == true + stepper.bindable.isContinuous.receiveValue(false) + expect(stepper.isContinuous) == false + } + + func testAutorepeat() { + let stepper: UIStepper = .init() + expect(stepper.autorepeat) == true + stepper.bindable.autorepeat.receiveValue(false) + expect(stepper.autorepeat) == false + } + + func testWraps() { + let stepper: UIStepper = .init() + expect(stepper.wraps) == false + stepper.bindable.wraps.receiveValue(true) + expect(stepper.wraps) == true + } + + func testMinimumValue() { + let stepper: UIStepper = .init() + expect(stepper.minimumValue) == 0 + stepper.bindable.minimumValue.receiveValue(1) + expect(stepper.minimumValue) == 1 + } + + func testMaximumValue() { + let stepper: UIStepper = .init() + expect(stepper.maximumValue) == 100 + stepper.bindable.maximumValue.receiveValue(200) + expect(stepper.maximumValue) == 200 + } + + func testStepValue() { + let stepper: UIStepper = .init() + expect(stepper.stepValue) == 1 + stepper.bindable.stepValue.receiveValue(10) + expect(stepper.stepValue) == 10 + } + + func testValue() { let stepper: UIStepper = .init() expect(stepper.value) == 0 stepper.bindable.value.receiveValue(1) diff --git a/Tests/CombineUITests/UIKit/BindingsTests/UISwitchTests.swift b/Tests/CombineUITests/UIKit/BindingsTests/UISwitchTests.swift index 8610f87..afa6816 100644 --- a/Tests/CombineUITests/UIKit/BindingsTests/UISwitchTests.swift +++ b/Tests/CombineUITests/UIKit/BindingsTests/UISwitchTests.swift @@ -8,6 +8,20 @@ import XCTest final class UISwitchTests: XCTestCase { + func testOnTintColor() { + let `switch`: UISwitch = .init() + expect(`switch`.onTintColor) == nil + `switch`.bindable.onTintColor.receiveValue(.systemPink) + expect(`switch`.onTintColor) == .systemPink + } + + func testThumbTintColor() { + let `switch`: UISwitch = .init() + expect(`switch`.thumbTintColor) == nil + `switch`.bindable.thumbTintColor.receiveValue(.systemPink) + expect(`switch`.thumbTintColor) == .systemPink + } + func testIsOn() { let `switch`: UISwitch = .init() expect(`switch`.isOn) == false diff --git a/Tests/CombineUITests/UIKit/BindingsTests/UITextFieldTests.swift b/Tests/CombineUITests/UIKit/BindingsTests/UITextFieldTests.swift index f5de5eb..566d84f 100644 --- a/Tests/CombineUITests/UIKit/BindingsTests/UITextFieldTests.swift +++ b/Tests/CombineUITests/UIKit/BindingsTests/UITextFieldTests.swift @@ -8,6 +8,42 @@ import XCTest final class UITextFieldTests: XCTestCase { + func testFont() { + let textField: UITextField = .init() + expect(textField.font) == .systemFont(ofSize: 17) + textField.bindable.font.receiveValue(.systemFont(ofSize: 23)) + expect(textField.font) == .systemFont(ofSize: 23) + } + + func testTextColor() { + let textField: UITextField = .init() + textField.textColor = .systemMint + expect(textField.textColor) == .systemMint + textField.bindable.textColor.receiveValue(.systemPink) + expect(textField.textColor) == .systemPink + } + + func testTextAlignment() { + let textField: UITextField = .init() + expect(textField.textAlignment) == .natural + textField.bindable.textAlignment.receiveValue(.left) + expect(textField.textAlignment) == .left + } + + func testPlaceholder() { + let textField: UITextField = .init() + expect(textField.placeholder) == nil + textField.bindable.placeholder.receiveValue("Text") + expect(textField.placeholder) == "Text" + } + + func testAttributedPlaceholder() { + let textField: UITextField = .init() + expect(textField.attributedPlaceholder?.string) == nil + textField.bindable.attributedPlaceholder.receiveValue(AttributedString("Attributed Text")) + expect(textField.attributedPlaceholder?.string) == "Attributed Text" + } + func testText() { let textField: UITextField = .init() expect(textField.text?.isEmpty) == true diff --git a/Tests/CombineUITests/UIKit/BindingsTests/UITextViewTests.swift b/Tests/CombineUITests/UIKit/BindingsTests/UITextViewTests.swift new file mode 100644 index 0000000..5f9e699 --- /dev/null +++ b/Tests/CombineUITests/UIKit/BindingsTests/UITextViewTests.swift @@ -0,0 +1,53 @@ +// +// Copyright © 2024 Tinder (Match Group, LLC) +// + +@testable import CombineUI +import Nimble +import XCTest + +final class UITextViewTests: XCTestCase { + + func testIsEditable() { + let textView: UITextView = .init() + expect(textView.isEditable) == true + textView.bindable.isEditable.receiveValue(false) + expect(textView.isEditable) == false + } + + func testFont() { + let textView: UITextView = .init() + expect(textView.font) == nil + textView.bindable.font.receiveValue(.systemFont(ofSize: 23)) + expect(textView.font) == .systemFont(ofSize: 23) + } + + func testTextColor() { + let textView: UITextView = .init() + textView.textColor = .systemMint + expect(textView.textColor) == .systemMint + textView.bindable.textColor.receiveValue(.systemPink) + expect(textView.textColor) == .systemPink + } + + func testTextAlignment() { + let textView: UITextView = .init() + expect(textView.textAlignment) == .natural + textView.bindable.textAlignment.receiveValue(.left) + expect(textView.textAlignment) == .left + } + + func testText() { + let textView: UITextView = .init() + expect(textView.text?.isEmpty) == true + textView.bindable.text.receiveValue("Text") + expect(textView.text) == "Text" + } + + func testAttributedText() { + let textView: UITextView = .init() + expect(textView.attributedText?.string.isEmpty) == true + textView.bindable.attributedText.receiveValue(AttributedString("Attributed Text")) + expect(textView.attributedText?.string) == "Attributed Text" + } +} diff --git a/Tests/CombineUITests/UIKit/BindingsTests/UIViewTests.swift b/Tests/CombineUITests/UIKit/BindingsTests/UIViewTests.swift index c006c48..eb4a0a9 100644 --- a/Tests/CombineUITests/UIKit/BindingsTests/UIViewTests.swift +++ b/Tests/CombineUITests/UIKit/BindingsTests/UIViewTests.swift @@ -8,6 +8,66 @@ import XCTest final class UIViewTests: XCTestCase { + func testIsUserInteractionEnabled() { + let view: UIView = .init() + expect(view.isUserInteractionEnabled) == true + view.bindable.isUserInteractionEnabled.receiveValue(false) + expect(view.isUserInteractionEnabled) == false + } + + func testIsMultipleTouchEnabled() { + let view: UIView = .init() + expect(view.isMultipleTouchEnabled) == false + view.bindable.isMultipleTouchEnabled.receiveValue(true) + expect(view.isMultipleTouchEnabled) == true + } + + func testIsExclusiveTouch() { + let view: UIView = .init() + expect(view.isExclusiveTouch) == false + view.bindable.isExclusiveTouch.receiveValue(true) + expect(view.isExclusiveTouch) == true + } + + func testClipsToBounds() { + let view: UIView = .init() + expect(view.clipsToBounds) == false + view.bindable.clipsToBounds.receiveValue(true) + expect(view.clipsToBounds) == true + } + + func testTintColor() { + let view: UIView = .init() + view.tintColor = .systemMint + expect(view.tintColor) == .systemMint + view.bindable.tintColor.receiveValue(.systemPink) + expect(view.tintColor) == .systemPink + } + + func testBackgroundColor() { + let view: UIView = .init() + view.backgroundColor = .systemMint + expect(view.backgroundColor) == .systemMint + view.bindable.backgroundColor.receiveValue(.systemPink) + expect(view.backgroundColor) == .systemPink + } + + func testBorderColor() { + let view: UIView = .init() + view.layer.borderColor = UIColor.systemMint.cgColor + expect(view.layer.borderColor) == UIColor.systemMint.cgColor + view.bindable.borderColor.receiveValue(.systemPink) + expect(view.layer.borderColor) == UIColor.systemPink.cgColor + } + + func testShadowColor() { + let view: UIView = .init() + view.layer.shadowColor = UIColor.systemMint.cgColor + expect(view.layer.shadowColor) == UIColor.systemMint.cgColor + view.bindable.shadowColor.receiveValue(.systemPink) + expect(view.layer.shadowColor) == UIColor.systemPink.cgColor + } + func testAlpha() { let view: UIView = .init() expect(view.alpha) == 1 @@ -15,6 +75,13 @@ final class UIViewTests: XCTestCase { expect(view.alpha) == 0.5 } + func testIsOpaque() { + let view: UIView = .init() + expect(view.isOpaque) == true + view.bindable.isOpaque.receiveValue(false) + expect(view.isOpaque) == false + } + func testIsHidden() { let view: UIView = .init() expect(view.isHidden) == false From 03a289a5645ca4ed832e6dcd0ac37bf3be167328 Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Sat, 13 Jan 2024 09:18:51 -0800 Subject: [PATCH 7/7] Remve periods from cheat sheets (#17) --- README.md | 192 +++++++++++++++++++++++++++--------------------------- 1 file changed, 96 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index a0d2a5a..326352c 100644 --- a/README.md +++ b/README.md @@ -157,417 +157,417 @@ Bindings - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -586,95 +586,95 @@ Extension Methods - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
BindingType
BindingType
UIButtonUIButton.titleColor(for: UIControl.State)UIColor
.titleShadowColor(for: UIControl.State)UIColor
.title(for: UIControl.State) String
AttributedString
.image(for: UIControl.State)UIImage?
.backgroundImage(for: UIControl.State)UIImage?
UIControl .isEnabled
UILabelUIImageView.imageUIImage?
.highlightedImageUIImage?
.isHighlightedBool
UILabel .isEnabled Bool
.fontUIFont
.textColorUIColor
.text String
UIPageControlUIPageControl.pageIndicatorTintColorUIColor
.currentPageIndicatorTintColorUIColor
.currentPage Int
UIProgressView.numberOfPagesInt
.hidesForSinglePageBool
UIProgressView.trackTintColorUIColor
.progressTintColorUIColor
.progress Float
UIRefreshControlUIRefreshControl.tintColorUIColor
.attributedTitleAttributedString
.isRefreshing Bool
UISegmentedControlUISegmentedControl.isMomentaryBool
.selectedSegmentIndex Int
UISlider.widthForSegment(at: Int)CGFloat
.titleForSegment(at: Int)String
.imageForSegment(at: Int)UIImage?
UISlider.isContinuousBool
.minimumValueFloat
.maximumValueFloat
.minimumTrackTintColorUIColor
.maximumTrackTintColorUIColor
.thumbTintColorUIColor
.value Float
UIStepperUIStepper.isContinuousBool
.autorepeatBool
.wrapsBool
.minimumValueDouble
.maximumValueDouble
.stepValueDouble
.value Double
UISwitchUISwitch.onTintColorUIColor
.thumbTintColorUIColor
.isOn Bool
UITextFieldUITextField.fontUIFont
.textColorUIColor
.textAlignmentNSTextAlignment
.placeholderString
.attributedPlaceholderAttributedString
.text String
UIViewUITextView.isEditableBool
.fontUIFont
.textColorUIColor
.textAlignmentNSTextAlignment
.textString
.attributedTextAttributedString
UIView.isUserInteractionEnabledBool
.isMultipleTouchEnabledBool
.isExclusiveTouchBool
.clipsToBoundsBool
.tintColorUIColor
.backgroundColorUIColor
.borderColorUIColor
.shadowColorUIColor
.alpha CGFloat
.isOpaqueBool
.isHidden Bool
UIButton.titleColor(for: UIControl.State)titleColor(for: UIControl.State) UIColor
.titleShadowColor(for: UIControl.State)titleShadowColor(for: UIControl.State) UIColor
.title(for: UIControl.State)title(for: UIControl.State) String
.attributedTitle(for: UIControl.State)attributedTitle(for: UIControl.State) AttributedString
.image(for: UIControl.State)image(for: UIControl.State) UIImage?
.backgroundImage(for: UIControl.State)backgroundImage(for: UIControl.State) UIImage?
UIControl.isEnabledisEnabled Bool
UIDatePicker.countDownDurationcountDownDuration TimeInterval
.datedate Date
.date(animated: Bool)date(animated: Bool) Date
UIGestureRecognizer.isEnabledisEnabled Bool
UIImageView.imageimage UIImage?
.highlightedImagehighlightedImage UIImage?
.isHighlightedisHighlighted Bool
UILabel.isEnabledisEnabled Bool
.fontfont UIFont
.textColortextColor UIColor
.texttext String
.attributedTextattributedText AttributedString
UIPageControl.pageIndicatorTintColorpageIndicatorTintColor UIColor
.currentPageIndicatorTintColorcurrentPageIndicatorTintColor UIColor
.currentPagecurrentPage Int
.numberOfPagesnumberOfPages Int
.hidesForSinglePagehidesForSinglePage Bool
UIProgressView.trackTintColortrackTintColor UIColor
.progressTintColorprogressTintColor UIColor
.progressprogress Float
.progress(animated: Bool)progress(animated: Bool) Float
UIRefreshControl.tintColortintColor UIColor
.attributedTitleattributedTitle AttributedString
.isRefreshingisRefreshing Bool
UISegmentedControl.isMomentaryisMomentary Bool
.selectedSegmentIndexselectedSegmentIndex Int
.isEnabledForSegment(at: Int)isEnabledForSegment(at: Int) Bool
.widthForSegment(at: Int)widthForSegment(at: Int) CGFloat
.titleForSegment(at: Int)titleForSegment(at: Int) String
.imageForSegment(at: Int)imageForSegment(at: Int) UIImage?
UISlider.isContinuousisContinuous Bool
.minimumValueminimumValue Float
.maximumValuemaximumValue Float
.minimumTrackTintColorminimumTrackTintColor UIColor
.maximumTrackTintColormaximumTrackTintColor UIColor
.thumbTintColorthumbTintColor UIColor
.valuevalue Float
.value(animated: Bool)value(animated: Bool) Float
UIStepper.isContinuousisContinuous Bool
.autorepeatautorepeat Bool
.wrapswraps Bool
.minimumValueminimumValue Double
.maximumValuemaximumValue Double
.stepValuestepValue Double
.valuevalue Double
UISwitch.onTintColoronTintColor UIColor
.thumbTintColorthumbTintColor UIColor
.isOnisOn Bool
.isOn(animated: Bool)isOn(animated: Bool) Bool
UITextField.fontfont UIFont
.textColortextColor UIColor
.textAlignmenttextAlignment NSTextAlignment
.placeholderplaceholder String
.attributedPlaceholderattributedPlaceholder AttributedString
.texttext String
.attributedTextattributedText AttributedString
UITextView.isEditableisEditable Bool
.fontfont UIFont
.textColortextColor UIColor
.textAlignmenttextAlignment NSTextAlignment
.texttext String
.attributedTextattributedText AttributedString
UIView.isUserInteractionEnabledisUserInteractionEnabled Bool
.isMultipleTouchEnabledisMultipleTouchEnabled Bool
.isExclusiveTouchisExclusiveTouch Bool
.clipsToBoundsclipsToBounds Bool
.tintColortintColor UIColor
.backgroundColorbackgroundColor UIColor
.borderColorborderColor UIColor
.shadowColorshadowColor UIColor
.alphaalpha CGFloat
.isOpaqueisOpaque Bool
.isHiddenisHidden Bool
UIButton.tapPublisher()tapPublisher() AnyPublisher<Void, Never>
UIControl.publisher(for: UIControl.Event)publisher(for: UIControl.Event) AnyPublisher<UIControl.Event, Never>
UIDatePicker.countDownDurationPublisher()countDownDurationPublisher() AnyPublisher<TimeInterval, Never>
.datePublisher()datePublisher() AnyPublisher<Date, Never>
UIGestureRecognizer.publisher(attachingTo: UIView)publisher(attachingTo: UIView) AnyPublisher<UIGestureRecognizer, Never>
UIPageControl.currentPagePublisher()currentPagePublisher() AnyPublisher<Int, Never>
UIProgressView.progressPublisher()progressPublisher() AnyPublisher<Float, Never>
UIRefreshControl.refreshPublisher()refreshPublisher() AnyPublisher<Void, Never>
UISegmentedControl.selectedSegmentIndexPublisher()selectedSegmentIndexPublisher() AnyPublisher<Int, Never>
UISlider.valuePublisher()valuePublisher() AnyPublisher<Float, Never>
UIStepper.valuePublisher()valuePublisher() AnyPublisher<Double, Never>
UISwitch.isOnPublisher()isOnPublisher() AnyPublisher<Bool, Never>
UITextField.textPublisher()textPublisher() AnyPublisher<String, Never>
.attributedTextPublisher()attributedTextPublisher() AnyPublisher<AttributedString, Never>
UIViewController.lifecyclePublisher()lifecyclePublisher() AnyPublisher<ViewControllerLifecycleEvent, Never>
Publisher where Output == ViewControllerLifecycleEvent.isVisiblePublisher()isVisiblePublisher() AnyPublisher<Bool, Never>