适用于 UI 手势、控件和视图的 Swift 属性包装器、绑定和 Combine 发布者
CombineUI 为诸如手势、控件和视图等选定的 Apple 类型提供 Swift 属性包装器。 这些属性包装器 投影 Combine 发布者,用于手势识别、控件事件、属性更改和委托回调。
示例
@Button var button = UIButton()
$button
投影值是一个发布者,当按钮被点击时会发送。
并提供绑定,用于使用来自 Combine 发布者的值更新选定的 Apple 视图的属性。
与通过 Combine 的 assign(to:on:)
方法订阅相比,通过 CombineUI 的绑定进行订阅更可取,因为它们使用 weak
引用并且可以接受方法参数。
示例
publisher.bind(to: button.bindable.title(for: .normal))
许多类型也通过 Swift 扩展增强,提供 Combine 发布者,可以在需要时用作属性包装器的替代方案。
目前支持 UIKit。 CombineUI 未来可能会支持其他 UI 框架。
15.0
5.8
包依赖
将
<version>
替换为所需的最低版本。
.package(url: "https://github.com/Tinder/CombineUI.git", from: "<version>")
目标依赖
"CombineUI"
https://swiftpackageindex.cn/Tinder/collection.json
所有示例都需要此设置
import Combine
import CombineUI
import UIKit
var cancellables = Set<AnyCancellable>()
@Button var button = UIButton()
button.setImage(UIImage(systemName: "heart"), for: .normal)
$button
.sink { print("Tapped") }
.store(in: &cancellables)
let label = UILabel()
let subject = PassthroughSubject<String, Never>()
subject
.bind(to: label.bindable.text)
.store(in: &cancellables)
subject.send("Text")
let viewController = UIViewController()
let lifecycle = viewController
.lifecyclePublisher()
.share()
lifecycle
.sink { print($0) } // .viewWillAppear | .viewDidAppear | .viewWillDisappear | .viewDidDisappear
.store(in: &cancellables)
lifecycle
.isVisiblePublisher()
.sink { print($0) } // true | false
.store(in: &cancellables)
其他示例可在 此存储库中包含的 Xcode 项目 中找到。 有关指导,请参阅包含设置说明的示例项目 README。
属性包装器
属性包装器 | 投影类型 | |
---|---|---|
UIButton |
@Button |
AnyPublisher<Void, Never> |
UIControl |
@Control |
ControlInterface |
UIDatePicker |
@DatePicker |
DatePickerInterface |
UIGestureRecognizer |
@GestureRecognizer |
GestureRecognizerInterface |
UIPageControl |
@PageControl |
AnyPublisher<Int, Never> |
UIRefreshControl |
@RefreshControl |
AnyPublisher<Void, Never> |
UIScrollView |
@ScrollView |
ScrollViewInterface |
UISearchBar |
@SearchBar |
SearchBarInterface |
UISegmentedControl |
@SegmentedControl |
AnyPublisher<Int, Never> |
UISlider |
@Slider |
AnyPublisher<Float, Never> |
UIStepper |
@Stepper |
AnyPublisher<Double, Never> |
UISwitch |
@Switch |
AnyPublisher<Bool, Never> |
UITextField |
@TextField |
TextFieldInterface |
UITextView |
@TextView |
TextViewInterface |
UIViewController |
@ViewController |
ViewControllerInterface |
绑定
绑定 | 类型 | |
---|---|---|
UIActivityIndicatorView |
style (样式) |
UIActivityIndicatorView.Style |
color (颜色) |
UIColor |
|
hidesWhenStopped (停止时隐藏) |
Bool |
|
isAnimating (是否正在动画) |
Bool |
|
UIButton |
titleColor(for: UIControl.State) (特定状态下的标题颜色) |
UIColor |
titleShadowColor(for: UIControl.State) (特定状态下的标题阴影颜色) |
UIColor |
|
title(for: UIControl.State) (特定状态下的标题) |
String |
|
attributedTitle(for: UIControl.State) (特定状态下的属性化标题) |
AttributedString |
|
image(for: UIControl.State) (特定状态下的图像) |
UIImage? |
|
backgroundImage(for: UIControl.State) (特定状态下的背景图像) |
UIImage? |
|
UIControl |
isEnabled (是否启用) |
Bool |
UIDatePicker |
countDownDuration (倒计时持续时间) |
TimeInterval |
date (日期) |
Date |
|
date(animated: Bool) (设置日期,是否动画) |
Date |
|
UIGestureRecognizer |
isEnabled (是否启用) |
Bool |
UIImageView |
image (图像) |
UIImage? |
highlightedImage (高亮图像) |
UIImage? |
|
isHighlighted (是否高亮) |
Bool |
|
UILabel |
isEnabled (是否启用) |
Bool |
font (字体) |
UIFont |
|
textColor (文本颜色) |
UIColor |
|
text (文本) |
String |
|
attributedText (属性化文本) |
AttributedString |
|
UIPageControl |
pageIndicatorTintColor (页面指示器颜色) |
UIColor |
currentPageIndicatorTintColor (当前页面指示器颜色) |
UIColor |
|
currentPage (当前页面) |
Int |
|
numberOfPages (页面数量) |
Int |
|
hidesForSinglePage (单页时隐藏) |
Bool |
|
UIProgressView |
trackTintColor (轨道颜色) |
UIColor |
progressTintColor (进度条颜色) |
UIColor |
|
progress (进度) |
Float |
|
progress(animated: Bool) (设置进度,是否动画) |
Float |
|
UIRefreshControl |
tintColor (着色颜色) |
UIColor |
attributedTitle (属性化标题) |
AttributedString |
|
isRefreshing (是否正在刷新) |
Bool |
|
UISegmentedControl |
isMomentary (是否瞬间) |
Bool |
selectedSegmentIndex (选中的段索引) |
Int |
|
isEnabledForSegment(at: Int) (指定索引的段是否启用) |
Bool |
|
widthForSegment(at: Int) (指定索引的段宽度) |
CGFloat |
|
titleForSegment(at: Int) (指定索引的段标题) |
String |
|
imageForSegment(at: Int) (指定索引的段图像) |
UIImage? |
|
UISlider |
isContinuous (是否连续) |
Bool |
minimumValue (最小值) |
Float |
|
maximumValue (最大值) |
Float |
|
minimumTrackTintColor (最小轨道颜色) |
UIColor |
|
maximumTrackTintColor (最大轨道颜色) |
UIColor |
|
thumbTintColor (滑块颜色) |
UIColor |
|
value (值) |
Float |
|
value(animated: Bool) (设置值,是否动画) |
Float |
|
UIStepper |
isContinuous (是否连续) |
Bool |
autorepeat (是否自动重复) |
Bool |
|
wraps (是否循环) |
Bool |
|
minimumValue (最小值) |
Double |
|
maximumValue (最大值) |
Double |
|
stepValue (步进值) |
Double |
|
value (值) |
Double |
|
UISwitch |
onTintColor (开启颜色) |
UIColor |
thumbTintColor (滑块颜色) |
UIColor |
|
isOn (是否开启) |
Bool |
|
isOn(animated: Bool) (设置是否开启,是否动画) |
Bool |
|
UITextField |
font (字体) |
UIFont |
textColor (文本颜色) |
UIColor |
|
textAlignment (文本对齐方式) |
NSTextAlignment |
|
placeholder (占位符) |
String |
|
attributedPlaceholder (属性化占位符) |
AttributedString |
|
text (文本) |
String |
|
attributedText (属性化文本) |
AttributedString |
|
UITextView |
isEditable (是否可编辑) |
Bool |
font (字体) |
UIFont |
|
textColor (文本颜色) |
UIColor |
|
textAlignment (文本对齐方式) |
NSTextAlignment |
|
text (文本) |
String |
|
attributedText (属性化文本) |
AttributedString |
|
UIView |
isUserInteractionEnabled (是否允许用户交互) |
Bool |
isMultipleTouchEnabled (是否允许多点触控) |
Bool |
|
isExclusiveTouch (是否独占触摸) |
Bool |
|
clipsToBounds (是否裁剪超出边界的内容) |
Bool |
|
tintColor (着色颜色) |
UIColor |
|
backgroundColor (背景颜色) |
UIColor |
|
borderColor (边框颜色) |
UIColor |
|
shadowColor (阴影颜色) |
UIColor |
|
alpha (透明度) |
CGFloat |
|
isOpaque (是否不透明) |
Bool |
|
isHidden (是否隐藏) |
Bool |
扩展方法
方法 | 类型 | |
---|---|---|
UIButton |
tapPublisher() (点击发布者) |
AnyPublisher<Void, Never> |
UIControl |
publisher(for: UIControl.Event) (指定事件的发布者) |
AnyPublisher<UIControl.Event, Never> |
UIDatePicker |
countDownDurationPublisher() (倒计时持续时间发布者) |
AnyPublisher<TimeInterval, Never> |
datePublisher() (日期发布者) |
AnyPublisher<Date, Never> |
|
UIGestureRecognizer |
publisher(attachingTo: UIView) (附加到 UIView 的发布者) |
AnyPublisher<UIGestureRecognizer, Never> |
UIPageControl |
currentPagePublisher() (当前页面发布者) |
AnyPublisher<Int, Never> |
UIRefreshControl |
refreshPublisher() (刷新发布者) |
AnyPublisher<Void, Never> |
UISegmentedControl |
selectedSegmentIndexPublisher() (选中段索引发布者) |
AnyPublisher<Int, Never> |
UISlider |
valuePublisher() (值发布者) |
AnyPublisher<Float, Never> |
UIStepper |
valuePublisher() (值发布者) |
AnyPublisher<Double, Never> |
UISwitch |
isOnPublisher() (是否开启发布者) |
AnyPublisher<Bool, Never> |
UITextField |
textPublisher() (文本发布者) |
AnyPublisher<String, Never> |
attributedTextPublisher() (属性化文本发布者) |
AnyPublisher<AttributedString, Never> |
|
UIViewController |
lifecyclePublisher() (生命周期事件发布者) |
AnyPublisher<ViewControllerLifecycleEvent, Never> |
Publisher where Output == ViewControllerLifecycleEvent (输出为 ViewControllerLifecycleEvent 的发布者) |
isVisiblePublisher() (可见性发布者) |
AnyPublisher<Bool, Never> |
var style: Binding<UIActivityIndicatorView.Style>
var color: Binding<UIColor>
var hidesWhenStopped: Binding<Bool>
var isAnimating: Binding<Bool>
let activityIndicatorView = UIActivityIndicatorView()
Just(.medium)
.bind(to: activityIndicatorView.bindable.style)
.store(in: &cancellables)
Just(.systemPink)
.bind(to: activityIndicatorView.bindable.color)
.store(in: &cancellables)
Just(true)
.bind(to: activityIndicatorView.bindable.hidesWhenStopped)
.store(in: &cancellables)
Just(true)
.bind(to: activityIndicatorView.bindable.isAnimating)
.store(in: &cancellables)
事件 |
---|
.primaryActionTriggered (主要操作触发) |
@Button // Projected Value: AnyPublisher<Void, Never>
func titleColor(for state: UIControl.State) -> Binding<UIColor>
func titleShadowColor(for state: UIControl.State) -> Binding<UIColor>
func title(for state: UIControl.State) -> Binding<String>
func attributedTitle(for state: UIControl.State) -> Binding<AttributedString>
func image(for state: UIControl.State) -> Binding<UIImage?>
func backgroundImage(for state: UIControl.State) -> Binding<UIImage?>
func tapPublisher() -> AnyPublisher<Void, Never>
// Property Wrapper
@Button var button = UIButton()
button.setImage(UIImage(systemName: "heart"), for: .normal)
$button
.sink { print("Tapped") }
.store(in: &cancellables)
// 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)
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
.tapPublisher()
.sink { print("Tapped") }
.store(in: &cancellables)
@Control // Projected Value: ControlInterface
ControlInterface
var touchDown: AnyPublisher<Void, Never>
var touchDownRepeat: AnyPublisher<Void, Never>
var touchDragInside: AnyPublisher<Void, Never>
var touchDragOutside: AnyPublisher<Void, Never>
var touchDragEnter: AnyPublisher<Void, Never>
var touchDragExit: AnyPublisher<Void, Never>
var touchUpInside: AnyPublisher<Void, Never>
var touchUpOutside: AnyPublisher<Void, Never>
var touchCancel: AnyPublisher<Void, Never>
var valueChanged: AnyPublisher<Void, Never>
var menuActionTriggered: AnyPublisher<Void, Never>
var primaryActionTriggered: AnyPublisher<Void, Never>
var editingDidBegin: AnyPublisher<Void, Never>
var editingChanged: AnyPublisher<Void, Never>
var editingDidEnd: AnyPublisher<Void, Never>
var editingDidEndOnExit: AnyPublisher<Void, Never>
var isEnabled: Binding<Bool>
func publisher(for controlEvents: UIControl.Event) -> AnyPublisher<UIControl.Event, Never>
在接收到的 OptionSet 上使用
contains()
来确定发生了哪个事件。
正如每个 CombineUI 属性包装器和绑定可以与其支持类型的子类一起使用一样,UIControl
属性包装器和绑定与 UIControl
子类兼容,包括(但不限于)以下内容
UIButton
UIDatePicker
UIPageControl
UIRefreshControl
UISegmentedControl
UISlider
UIStepper
UISwitch
UITextField
// Property Wrapper
@Control var control = UIButton()
control.setImage(UIImage(systemName: "heart"), for: .normal)
$control
.primaryActionTriggered
.sink { print("Triggered") }
.store(in: &cancellables)
// Binding
Just(true)
.bind(to: control.bindable.isEnabled)
.store(in: &cancellables)
// Extension Method
control
.publisher(for: .primaryActionTriggered)
.sink { controlEvents in }
.store(in: &cancellables)
UIControl.Event
类型的发布者时才使用 @Control
属性包装器,否则应首选特定类型的属性包装器。属性 | 事件 |
---|---|
.countDownDuration (倒计时持续时间) |
.valueChanged (值改变) |
.date (日期) |
.valueChanged (值改变) |
@DatePicker(mode: UIDatePicker.Mode = .dateAndTime) // Projected Value: DatePickerInterface
DatePickerInterface
var countDownDuration: AnyPublisher<TimeInterval, Never>
var date: AnyPublisher<Date, Never>
var countDownDuration: Binding<TimeInterval>
var date: Binding<Date>
func date(animated: Bool) -> Binding<Date>
func datePublisher() -> AnyPublisher<Date, Never>
func countDownDurationPublisher() -> AnyPublisher<TimeInterval, Never>
// Property Wrapper
@DatePicker(mode: .countDownTimer)
var datePicker1 = UIDatePicker()
@DatePicker var datePicker2 = UIDatePicker()
$datePicker1
.countDownDuration
.sink { countDownDuration in }
.store(in: &cancellables)
$datePicker2
.date
.sink { date in }
.store(in: &cancellables)
// Bindings
Just(60)
.bind(to: datePicker1.bindable.countDownDuration)
.store(in: &cancellables)
Just(Date())
.bind(to: datePicker2.bindable.date)
.store(in: &cancellables)
Just(Date())
.bind(to: datePicker2.bindable.date(animated: true))
.store(in: &cancellables)
// Extension Methods
datePicker1
.countDownDurationPublisher()
.sink { countDownDuration in }
.store(in: &cancellables)
datePicker2
.datePublisher()
.sink { date in }
.store(in: &cancellables)
@GestureRecognizer // Projected Value: GestureRecognizerInterface
GestureRecognizerInterface
func attaching(to view: UIView) -> AnyPublisher<UIGestureRecognizer, Never>
var isEnabled: Binding<Bool>
func publisher(attachingTo view: UIView) -> AnyPublisher<UIGestureRecognizer, Never>
// Property Wrapper
@GestureRecognizer var swipe = UISwipeGestureRecognizer()
$swipe
.attaching(to: view)
.sink { swipe in }
.store(in: &cancellables)
// Binding
Just(true)
.bind(to: swipe.bindable.isEnabled)
.store(in: &cancellables)
// Extension Method
swipe
.publisher(attachingTo: view)
.sink { swipe in }
.store(in: &cancellables)
var image: Binding<UIImage?>
var highlightedImage: Binding<UIImage?>
var isHighlighted: Binding<Bool>
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)
var isEnabled: Binding<Bool>
var font: Binding<UIFont>
var textColor: Binding<UIColor>
var text: Binding<String>
var attributedText: Binding<AttributedString>
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(AttributedString("Text"))
.bind(to: label.bindable.attributedText)
.store(in: &cancellables)
属性 | 事件 |
---|---|
.currentPage (当前页面) |
.valueChanged (值改变) |
@PageControl // Projected Value: AnyPublisher<Int, Never>
var pageIndicatorTintColor: Binding<UIColor>
var currentPageIndicatorTintColor: Binding<UIColor>
var currentPage: Binding<Int>
var numberOfPages: Binding<Int>
var hidesForSinglePage: Binding<Bool>
func currentPagePublisher() -> AnyPublisher<Int, Never>
// Property Wrapper
@PageControl var pageControl = UIPageControl()
$pageControl
.sink { currentPage in }
.store(in: &cancellables)
// 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
.currentPagePublisher()
.sink { currentPage in }
.store(in: &cancellables)
var trackTintColor: Binding<UIColor>
var progressTintColor: Binding<UIColor>
var progress: Binding<Float>
func progress(animated: Bool) -> Binding<Float>
let progressView = UIProgressView()
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)
Just(1)
.bind(to: progressView.bindable.progress(animated: true))
.store(in: &cancellables)
属性 | 事件 | 值 |
---|---|---|
.isRefreshing (是否正在刷新) |
.valueChanged (值改变) |
true (真) |
@RefreshControl // Projected Value: AnyPublisher<Void, Never>
var tintColor: Binding<UIColor>
var attributedTitle: Binding<AttributedString>
var isRefreshing: Binding<Bool>
func refreshPublisher() -> AnyPublisher<Void, Never>
// Property Wrapper
@RefreshControl var refreshControl = UIRefreshControl()
$refreshControl
.sink { print("Refreshing") }
.store(in: &cancellables)
// 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)
.store(in: &cancellables)
// Extension Method
refreshControl
.refreshPublisher()
.sink { print("Refreshing") }
.store(in: &cancellables)
协议 |
---|
UIScrollViewDelegate |
@ScrollView // Projected Value: ScrollViewInterface
ScrollViewInterface
var didScroll: AnyPublisher<Void, Never>
var didZoom: AnyPublisher<Void, Never>
var willBeginDragging: AnyPublisher<Void, Never>
var willEndDragging: AnyPublisher<ScrollViewWillEndDragging, Never>
var didEndDragging: AnyPublisher<Bool, Never>
var willBeginDecelerating: AnyPublisher<Void, Never>
var didEndDecelerating: AnyPublisher<Void, Never>
var didEndScrollingAnimation: AnyPublisher<Void, Never>
var willBeginZooming: AnyPublisher<UIView?, Never>
var didEndZooming: AnyPublisher<ScrollViewDidEndZooming, Never>
var didScrollToTop: AnyPublisher<Void, Never>
var didChangeAdjustedContentInset: AnyPublisher<Void, Never>
@ScrollView var scrollView = UIScrollView()
$scrollView
.didScroll
.sink { print("Scrolling") }
.store(in: &cancellables)
协议 |
---|
UISearchBarDelegate |
@SearchBar // Projected Value: SearchBarInterface
SearchBarInterface
var textDidBeginEditing: AnyPublisher<Void, Never>
var textDidEndEditing: AnyPublisher<Void, Never>
var textDidChange: AnyPublisher<String, Never>
var searchButtonClicked: AnyPublisher<Void, Never>
var bookmarkButtonClicked: AnyPublisher<Void, Never>
var cancelButtonClicked: AnyPublisher<Void, Never>
var resultsListButtonClicked: AnyPublisher<Void, Never>
var selectedScopeButtonIndexDidChange: AnyPublisher<Int, Never>
@SearchBar var searchBar = UISearchBar()
$searchBar
.textDidChange
.sink { text in }
.store(in: &cancellables)
属性 | 事件 |
---|---|
.selectedSegmentIndex (选中段索引) |
.valueChanged (值改变) |
@SegmentedControl // Projected Value: AnyPublisher<Int, Never>
var isMomentary: Binding<Bool>
var selectedSegmentIndex: Binding<Int>
func isEnabledForSegment(at index: Int) -> Binding<Bool>
func widthForSegment(at index: Int) -> Binding<CGFloat>
func titleForSegment(at index: Int) -> Binding<String>
func imageForSegment(at index: Int) -> Binding<UIImage?>
func selectedSegmentIndexPublisher() -> AnyPublisher<Int, Never>
// Property Wrapper
@SegmentedControl var segmentedControl = UISegmentedControl(items: items)
$segmentedControl
.sink { selectedSegmentIndex in }
.store(in: &cancellables)
// Bindings
Just(true)
.bind(to: segmentedControl.bindable.isMomentary)
.store(in: &cancellables)
Just(1)
.bind(to: segmentedControl.bindable.selectedSegmentIndex)
.store(in: &cancellables)
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
.selectedSegmentIndexPublisher()
.sink { selectedSegmentIndex in }
.store(in: &cancellables)
属性 | 事件 |
---|---|
.value (值) |
.valueChanged (值改变) |
@Slider // Projected Value: AnyPublisher<Float, Never>
var isContinuous: Binding<Bool>
var minimumValue: Binding<Float>
var maximumValue: Binding<Float>
var minimumTrackTintColor: Binding<UIColor>
var maximumTrackTintColor: Binding<UIColor>
var thumbTintColor: Binding<UIColor>
var value: Binding<Float>
func value(animated: Bool) -> Binding<Float>
func valuePublisher() -> AnyPublisher<Float, Never>
// Property Wrapper
@Slider var slider = UISlider()
$slider
.sink { value in }
.store(in: &cancellables)
// 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)
Just(1)
.bind(to: slider.bindable.value(animated: true))
.store(in: &cancellables)
// Extension Method
slider
.valuePublisher()
.sink { value in }
.store(in: &cancellables)
属性 | 事件 |
---|---|
.value (值) |
.valueChanged (值改变) |
@Stepper // Projected Value: AnyPublisher<Double, Never>
var isContinuous: Binding<Bool>
var autorepeat: Binding<Bool>
var wraps: Binding<Bool>
var minimumValue: Binding<Double>
var maximumValue: Binding<Double>
var stepValue: Binding<Double>
var value: Binding<Double>
func valuePublisher() -> AnyPublisher<Double, Never>
// Property Wrapper
@Stepper var stepper = UIStepper()
$stepper
.sink { value in }
.store(in: &cancellables)
// 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)
.store(in: &cancellables)
// Extension Method
stepper
.valuePublisher()
.sink { value in }
.store(in: &cancellables)
属性 | 事件 |
---|---|
.isOn (是否开启) |
.valueChanged (值改变) |
@Switch // Projected Value: AnyPublisher<Bool, Never>
var onTintColor: Binding<UIColor>
var thumbTintColor: Binding<UIColor>
var isOn: Binding<Bool>
func isOn(animated: Bool) -> Binding<Bool>
func isOnPublisher() -> AnyPublisher<Bool, Never>
// Property Wrapper
@Switch var `switch` = UISwitch()
$switch
.sink { isOn in }
.store(in: &cancellables)
// 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)
Just(true)
.bind(to: `switch`.bindable.isOn(animated: true))
.store(in: &cancellables)
// Extension Method
`switch`
.isOnPublisher()
.sink { isOn in }
.store(in: &cancellables)
属性 | 事件 |
---|---|
.text (文本) |
.allEditingEvents (所有编辑事件) |
.attributedText (属性化文本) |
.allEditingEvents (所有编辑事件) |
协议 |
---|
UITextFieldDelegate |
@TextField // Projected Value: TextFieldInterface
TextFieldInterface
// Properties
var text: AnyPublisher<String, Never>
var attributedText: AnyPublisher<AttributedString, Never>
// UITextFieldDelegate
var didBeginEditing: AnyPublisher<Void, Never>
var didEndEditing: AnyPublisher<Void, Never>
var didChangeSelection: AnyPublisher<Void, Never>
var font: Binding<UIFont>
var textColor: Binding<UIColor>
var textAlignment: Binding<NSTextAlignment>
var placeholder: Binding<String>
var attributedPlaceholder: Binding<AttributedString>
var text: Binding<String>
var attributedText: Binding<AttributedString>
func textPublisher() -> AnyPublisher<String, Never>
func attributedTextPublisher() -> AnyPublisher<AttributedString, Never>
// Property Wrapper
@TextField var textField = UITextField()
$textField
.text
.sink { text in }
.store(in: &cancellables)
$textField
.attributedText
.sink { attributedText in }
.store(in: &cancellables)
// 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)
Just(AttributedString("Text"))
.bind(to: textField.bindable.attributedText)
.store(in: &cancellables)
// Extension Methods
textField
.textPublisher()
.sink { text in }
.store(in: &cancellables)
textField
.attributedTextPublisher()
.sink { attributedText in }
.store(in: &cancellables)
协议 |
---|
UITextViewDelegate |
@TextView // Projected Value: TextViewInterface
TextViewInterface
// Properties
var text: AnyPublisher<String, Never>
var attributedText: AnyPublisher<AttributedString, Never>
// UITextViewDelegate
var didChange: AnyPublisher<Void, Never>
var didBeginEditing: AnyPublisher<Void, Never>
var didEndEditing: AnyPublisher<Void, Never>
var didChangeSelection: AnyPublisher<Void, Never>
var isEditable: Binding<Bool>
var font: Binding<UIFont>
var textColor: Binding<UIColor>
var textAlignment: Binding<NSTextAlignment>
var text: Binding<String>
var attributedText: Binding<AttributedString>
// Property Wrapper
@TextView var textView = UITextView()
$textView
.text
.sink { text in }
.store(in: &cancellables)
$textView
.attributedText
.sink { attributedText in }
.store(in: &cancellables)
// Bindings
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)
var isUserInteractionEnabled: Binding<Bool>
var isMultipleTouchEnabled: Binding<Bool>
var isExclusiveTouch: Binding<Bool>
var clipsToBounds: Binding<Bool>
var tintColor: Binding<UIColor>
var backgroundColor: Binding<UIColor>
var borderColor: Binding<UIColor>
var shadowColor: Binding<UIColor>
var alpha: Binding<CGFloat>
var isOpaque: Binding<Bool>
var isHidden: Binding<Bool>
正如每个 CombineUI 绑定可以与其支持类型的子类一起使用一样,UIView
绑定与所有 UIView
子类兼容。
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)
@ViewController // Projected Value: ViewControllerInterface
ViewControllerInterface
var isVisible: AnyPublisher<Bool, Never>
var viewWillAppear: AnyPublisher<Void, Never>
var viewDidAppear: AnyPublisher<Void, Never>
var viewWillDisappear: AnyPublisher<Void, Never>
var viewDidDisappear: AnyPublisher<Void, Never>
func lifecyclePublisher() -> AnyPublisher<ViewControllerLifecycleEvent, Never>
func isVisiblePublisher() -> AnyPublisher<Bool, Failure> where Output == ViewControllerLifecycleEvent
// Property Wrapper
@ViewController var viewController = UIViewController()
$viewController
.isVisible
.sink { isVisible in }
.store(in: &cancellables)
$viewController
.viewWillAppear
.sink { print("Appearing") }
.store(in: &cancellables)
$viewController
.viewDidAppear
.sink { print("Appeared") }
.store(in: &cancellables)
$viewController
.viewWillDisappear
.sink { print("Disappearing") }
.store(in: &cancellables)
$viewController
.viewDidDisappear
.sink { print("Disappeared") }
.store(in: &cancellables)
// Extension Methods
let lifecycle = viewController
.lifecyclePublisher()
.share()
lifecycle
.sink { event in }
.store(in: &cancellables)
lifecycle
.isVisiblePublisher()
.sink { isVisible in }
.store(in: &cancellables)
每个订阅都会使用视图控制器容器将一个辅助视图(零大小)添加到视图层次结构中,因此当有多个订阅者时,请考虑共享订阅(如上面的示例所示)。
与其他属性包装器形成鲜明对比的是,@ViewController
属性包装器被认为不如扩展方法有用。 这是因为扩展方法可以在视图控制器实例中使用,以订阅其自身的生命周期事件,例如
override func viewDidLoad() {
super.viewDidLoad()
lifecyclePublisher()
.isVisiblePublisher()
.sink { isVisible in }
.store(in: &cancellables)
}
CombineUI 为常见的委托协议方法提供发布者,但是由于发布者的性质,具有返回值的委托方法不可用作发布者。 此外,CombineUI 提供的委托方法发布者仅作为一种便利方式提供。 对于复杂的设置,甚至当需要超过少数几个委托方法时,建议使用实际的委托类实例。
另请注意,设置委托属性将禁用委托发布者。 这意味着不可能将实际的委托类实例与委托发布者一起使用。 因此,针对每个特定用例选择一种模式或另一种模式。
可以轻松地将其他绑定添加到现有视图和控件。 这对于 CombineUI 尚未原生支持的属性很有用。
示例
extension Bindable where Target: UIView {
var tag: Binding<Int> {
Binding(self, for: \.tag)
}
}
自定义视图和控件也可以采用 CombineUI 提供的相同类型的 API。
示例
@propertyWrapper
struct Example<T: ExampleControl> {
var wrappedValue: T
var projectedValue: AnyPublisher<ExampleValue, Never>
init(wrappedValue: T) {
self.wrappedValue = wrappedValue
self.projectedValue = wrappedValue
.valuePublisher()
.share()
.eraseToAnyPublisher()
}
}
extension Bindable where Target: ExampleControl {
var value: Binding<ExampleValue> {
Binding(self, for: \.value)
}
}
extension ExampleControl {
func valuePublisher() -> AnyPublisher<ExampleValue, Never> {
publisher(for: .valueChanged)
.compactMap { [weak self] _ in self?.value }
.prepend(value)
.eraseToAnyPublisher()
}
}
CombineUI 源代码可用作其他示例的参考。
虽然非常感谢您对贡献该项目的兴趣,但它仅为了与社区分享的目的而开源。 这意味着我们目前无法接受外部贡献,并且不会审查或合并拉取请求。 要报告安全问题或漏洞,请提交 GitHub 问题。