https://github.com/tinder/combineui
Swift Property Wrappers, Bindings and Combine Publishers for UI Gestures, Controls and Views
https://github.com/tinder/combineui
Last synced: 4 months ago
JSON representation
Swift Property Wrappers, Bindings and Combine Publishers for UI Gestures, Controls and Views
- Host: GitHub
- URL: https://github.com/tinder/combineui
- Owner: Tinder
- License: other
- Created: 2023-04-14T17:54:47.000Z (about 3 years ago)
- Default Branch: main
- Last Pushed: 2026-02-18T17:06:35.000Z (4 months ago)
- Last Synced: 2026-02-18T20:37:26.162Z (4 months ago)
- Language: Swift
- Homepage:
- Size: 614 KB
- Stars: 12
- Watchers: 1
- Forks: 3
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README
[](https://github.com/Tinder/CombineUI/actions/workflows/swift.yml)
[](https://github.com/Tinder/CombineUI/actions/workflows/docc.yml)
# CombineUI
Swift Property Wrappers, Bindings and Combine Publishers for UI Gestures, Controls and Views
## Overview
CombineUI provides [Swift property wrappers](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/properties/#Property-Wrappers) for select Apple types such as gestures, controls and views. These property wrappers [project](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/properties/#Projecting-a-Value-From-a-Property-Wrapper) Combine publishers for gesture recognition, control events, property changes and delegate callbacks.
Example:
```swift
@Button var button = UIButton()
```
> The `$button` projected value is a publisher that will send when the button is tapped.
And bindings are provided for updating properties of select Apple views with values from Combine publishers.
- The property wrappers are for __*publishing*__ (i.e. to publish a value when a UI element changes).
- The bindings are for __*subscribing*__ (i.e. to update a UI element with a published value).
### Bindings Benefits
As compared to subscribing via Combine's' [`assign(to:on:)`](https://developer.apple.com/documentation/combine/publisher/assign(to:on:)) method, subscribing via CombineUI's bindings is preferable since they use `weak` references and can accept method arguments.
Example:
```swift
publisher.bind(to: button.bindable.title(for: .normal))
```
### Swift Extensions
Many types are also enhanced with Swift extensions providing Combine publishers that may be used as an alternative to the property wrappers when needed.
### Supported UI Frameworks
UIKit is currently supported. CombineUI may support additional UI frameworks in the future.
## Installation
### Swift Package Collection
Expand / Collapse
```
https://swiftpackageindex.com/Tinder/collection.json
```
### Swift Package Manager
Expand / Collapse
**Package Dependency**
> Replace `` with the desired minimum version.
```swift
.package(url: "https://github.com/Tinder/CombineUI.git", from: "")
```
**Target Dependency**
```swift
"CombineUI"
```
### Xcode
Expand / Collapse
**Package Dependency**
```
https://github.com/Tinder/CombineUI.git
```
**Target Dependency**
```
CombineUI
```
## Code Examples
All examples require this setup:
```swift
import Combine
import CombineUI
import UIKit
var cancellables = Set()
```
### Property Wrapper Example: `UIButton`
```swift
@Button var button = UIButton()
button.setImage(UIImage(systemName: "heart"), for: .normal)
$button
.sink { print("Tapped") }
.store(in: &cancellables)
```
### Binding Example: `UILabel`
```swift
let label = UILabel()
let subject = PassthroughSubject()
subject
.bind(to: label.bindable.text)
.store(in: &cancellables)
subject.send("Text")
```
### Extension Methods Example: `UIViewController`
```swift
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)
```
### Comprehensive Examples
Additional examples are available within [an Xcode project included in this repository](Example/). See the [example project README containing setup instructions](Example/) for guidance.

## Cheat Sheets
### UIKit
Property Wrappers
| | Property Wrapper | Projected Type |
| :-- | :-- | :-- |
| `UIButton` | `@Button` | `AnyPublisher` |
| `UIControl` | `@Control` | `ControlInterface` |
| `UIDatePicker` | `@DatePicker` | `DatePickerInterface` |
| `UIGestureRecognizer` | `@GestureRecognizer` | `GestureRecognizerInterface` |
| `UIPageControl` | `@PageControl` | `AnyPublisher` |
| `UIRefreshControl` | `@RefreshControl` | `AnyPublisher` |
| `UIScrollView` | `@ScrollView` | `ScrollViewInterface` |
| `UISearchBar` | `@SearchBar` | `SearchBarInterface` |
| `UISegmentedControl` | `@SegmentedControl` | `AnyPublisher` |
| `UISlider` | `@Slider` | `AnyPublisher` |
| `UIStepper` | `@Stepper` | `AnyPublisher` |
| `UISwitch` | `@Switch` | `AnyPublisher` |
| `UITextField` | `@TextField` | `TextFieldInterface` |
| `UITextView` | `@TextView` | `TextViewInterface` |
| `UIViewController` | `@ViewController` | `ViewControllerInterface` |
Bindings
Binding
Type
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
Extension Methods
Method
Type
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)
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
isVisiblePublisher()
AnyPublisher<Bool, Never>
## Documentation
- UIKit
- [UIActivityIndicatorView](#uiactivityindicatorview)
- [UIButton](#uibutton)
- [UIControl](#uicontrol)
- [UIDatePicker](#uidatepicker)
- [UIGestureRecognizer](#uigesturerecognizer)
- [UIImageView](#uiimageview)
- [UILabel](#uilabel)
- [UIPageControl](#uipagecontrol)
- [UIProgressView](#uiprogressview)
- [UIRefreshControl](#uirefreshcontrol)
- [UIScrollView](#uiscrollview)
- [UISearchBar](#uisearchbar)
- [UISegmentedControl](#uisegmentedcontrol)
- [UISlider](#uislider)
- [UIStepper](#uistepper)
- [UISwitch](#uiswitch)
- [UITextField](#uitextfield)
- [UITextView](#uitextview)
- [UIView](#uiview)
- [UIViewController](#uiviewcontroller)
- [Caveats](#caveats)
- [Customization](#customization)
## `UIActivityIndicatorView`
### Bindings
```swift
var style: Binding
var color: Binding
var hidesWhenStopped: Binding
var isAnimating: Binding
```
### Code Example
```swift
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)
```
## `UIButton`
| Event |
| :-- |
| `.primaryActionTriggered` |
### Property Wrapper
```swift
@Button // Projected Value: AnyPublisher
```
### 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
```swift
func tapPublisher() -> AnyPublisher
```
### Code Example
```swift
// 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)
```
## `UIControl`
### Property Wrapper
```swift
@Control // Projected Value: ControlInterface
```
`ControlInterface`
```swift
var touchDown: AnyPublisher
var touchDownRepeat: AnyPublisher
var touchDragInside: AnyPublisher
var touchDragOutside: AnyPublisher
var touchDragEnter: AnyPublisher
var touchDragExit: AnyPublisher
var touchUpInside: AnyPublisher
var touchUpOutside: AnyPublisher
var touchCancel: AnyPublisher
var valueChanged: AnyPublisher
var menuActionTriggered: AnyPublisher
var primaryActionTriggered: AnyPublisher
var editingDidBegin: AnyPublisher
var editingChanged: AnyPublisher
var editingDidEnd: AnyPublisher
var editingDidEndOnExit: AnyPublisher
```
### Binding
```swift
var isEnabled: Binding
```
### Extension Method
```swift
func publisher(for controlEvents: UIControl.Event) -> AnyPublisher
```
> Use [`contains()`](https://developer.apple.com/documentation/swift/setalgebra/contains(_:)-5usmy) on the [OptionSet](https://developer.apple.com/documentation/swift/optionset) that is received to determine which event occurred.
### Supported Types
Just as every CombineUI property wrapper and binding may be used with subclasses of their supported type, the `UIControl` property wrapper and binding are compatible with `UIControl` subclasses, including (but not limited to) the following:
- `UIButton`
- `UIDatePicker`
- `UIPageControl`
- `UIRefreshControl`
- `UISegmentedControl`
- `UISlider`
- `UIStepper`
- `UISwitch`
- `UITextField`
### Code Example
```swift
// 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)
```
### Notes
- Use the `@Control` property wrapper only when publishers for the `UIControl.Event` types are needed, otherwise the type specific property wrappers should be preferred.
## `UIDatePicker`
| Property | Event |
| :-- | :-- |
| `.countDownDuration` | `.valueChanged` |
| `.date` | `.valueChanged` |
### Property Wrapper
```swift
@DatePicker(mode: UIDatePicker.Mode = .dateAndTime) // Projected Value: DatePickerInterface
```
`DatePickerInterface`
```swift
var countDownDuration: AnyPublisher
var date: AnyPublisher
```
### Bindings
```swift
var countDownDuration: Binding
var date: Binding
func date(animated: Bool) -> Binding
```
### Extension Methods
```swift
func datePublisher() -> AnyPublisher
func countDownDurationPublisher() -> AnyPublisher
```
### Code Example
```swift
// 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)
```
## `UIGestureRecognizer`
### Property Wrapper
```swift
@GestureRecognizer // Projected Value: GestureRecognizerInterface
```
`GestureRecognizerInterface`
```swift
func attaching(to view: UIView) -> AnyPublisher
```
### Binding
```swift
var isEnabled: Binding
```
### Extension Method
```swift
func publisher(attachingTo view: UIView) -> AnyPublisher
```
### Code Example
```swift
// 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)
```
### Notes
- 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
```
### Code Example
```swift
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)
```
## `UIPageControl`
| Property | Event |
| :-- | :-- |
| `.currentPage` | `.valueChanged` |
### Property Wrapper
```swift
@PageControl // Projected Value: AnyPublisher
```
### Bindings
```swift
var pageIndicatorTintColor: Binding
var currentPageIndicatorTintColor: Binding
var currentPage: Binding
var numberOfPages: Binding
var hidesForSinglePage: Binding
```
### Extension Method
```swift
func currentPagePublisher() -> AnyPublisher
```
### Code Example
```swift
// 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)
```
## `UIProgressView`
### Bindings
```swift
var trackTintColor: Binding
var progressTintColor: Binding
var progress: Binding
func progress(animated: Bool) -> Binding
```
### Code Example
```swift
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)
```
## `UIRefreshControl`
| Property | Event | Value |
| :-- | :-- | :-- |
| `.isRefreshing` | `.valueChanged` | `true` |
### Property Wrapper
```swift
@RefreshControl // Projected Value: AnyPublisher
```
### Bindings
```swift
var tintColor: Binding
var attributedTitle: Binding
var isRefreshing: Binding
```
### Extension Method
```swift
func refreshPublisher() -> AnyPublisher
```
### Code Example
```swift
// 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)
```
## `UIScrollView`
| Protocol |
| :-- |
| `UIScrollViewDelegate` |
### Property Wrapper
```swift
@ScrollView // Projected Value: ScrollViewInterface
```
`ScrollViewInterface`
```swift
var didScroll: AnyPublisher
var didZoom: AnyPublisher
var willBeginDragging: AnyPublisher
var willEndDragging: AnyPublisher
var didEndDragging: AnyPublisher
var willBeginDecelerating: AnyPublisher
var didEndDecelerating: AnyPublisher
var didEndScrollingAnimation: AnyPublisher
var willBeginZooming: AnyPublisher
var didEndZooming: AnyPublisher
var didScrollToTop: AnyPublisher
var didChangeAdjustedContentInset: AnyPublisher
```
### Code Example
```swift
@ScrollView var scrollView = UIScrollView()
$scrollView
.didScroll
.sink { print("Scrolling") }
.store(in: &cancellables)
```
## `UISearchBar`
| Protocol |
| :-- |
| `UISearchBarDelegate` |
### Property Wrapper
```swift
@SearchBar // Projected Value: SearchBarInterface
```
`SearchBarInterface`
```swift
var textDidBeginEditing: AnyPublisher
var textDidEndEditing: AnyPublisher
var textDidChange: AnyPublisher
var searchButtonClicked: AnyPublisher
var bookmarkButtonClicked: AnyPublisher
var cancelButtonClicked: AnyPublisher
var resultsListButtonClicked: AnyPublisher
var selectedScopeButtonIndexDidChange: AnyPublisher
```
### Code Example
```swift
@SearchBar var searchBar = UISearchBar()
$searchBar
.textDidChange
.sink { text in }
.store(in: &cancellables)
```
## `UISegmentedControl`
| Property | Event |
| :-- | :-- |
| `.selectedSegmentIndex` | `.valueChanged` |
### Property Wrapper
```swift
@SegmentedControl // Projected Value: AnyPublisher
```
### 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
```swift
func selectedSegmentIndexPublisher() -> AnyPublisher
```
### Code Example
```swift
// 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)
```
## `UISlider`
| Property | Event |
| :-- | :-- |
| `.value` | `.valueChanged` |
### Property Wrapper
```swift
@Slider // Projected Value: AnyPublisher
```
### 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
```
### Extension Method
```swift
func valuePublisher() -> AnyPublisher
```
### Code Example
```swift
// 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)
```
## `UIStepper`
| Property | Event |
| :-- | :-- |
| `.value` | `.valueChanged` |
### Property Wrapper
```swift
@Stepper // Projected Value: AnyPublisher
```
### Bindings
```swift
var isContinuous: Binding
var autorepeat: Binding
var wraps: Binding
var minimumValue: Binding
var maximumValue: Binding
var stepValue: Binding
var value: Binding
```
### Extension Method
```swift
func valuePublisher() -> AnyPublisher
```
### Code Example
```swift
// 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)
```
## `UISwitch`
| Property | Event |
| :-- | :-- |
| `.isOn` | `.valueChanged` |
### Property Wrapper
```swift
@Switch // Projected Value: AnyPublisher
```
### Bindings
```swift
var onTintColor: Binding
var thumbTintColor: Binding
var isOn: Binding
func isOn(animated: Bool) -> Binding
```
### Extension Method
```swift
func isOnPublisher() -> AnyPublisher
```
### Code Example
```swift
// 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)
```
## `UITextField`
| Property | Events |
| :-- | :-- |
| `.text` | `.allEditingEvents` |
| `.attributedText` | `.allEditingEvents` |
| Protocol |
| :-- |
| `UITextFieldDelegate` |
### Property Wrapper
```swift
@TextField // Projected Value: TextFieldInterface
```
`TextFieldInterface`
```swift
// Properties
var text: AnyPublisher
var attributedText: AnyPublisher
// UITextFieldDelegate
var didBeginEditing: AnyPublisher
var didEndEditing: AnyPublisher
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
```
### Extension Methods
```swift
func textPublisher() -> AnyPublisher
func attributedTextPublisher() -> AnyPublisher
```
### Code Example
```swift
// 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)
```
## `UITextView`
| Protocol |
| :-- |
| `UITextViewDelegate` |
### Property Wrapper
```swift
@TextView // Projected Value: TextViewInterface
```
`TextViewInterface`
```swift
// Properties
var text: AnyPublisher
var attributedText: AnyPublisher
// UITextViewDelegate
var didChange: AnyPublisher
var didBeginEditing: AnyPublisher
var didEndEditing: AnyPublisher
var didChangeSelection: AnyPublisher
```
### Bindings
```swift
var isEditable: Binding
var font: Binding
var textColor: Binding
var textAlignment: Binding
var text: Binding
var attributedText: Binding
```
### Code Example
```swift
// 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)
```
## `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
```
### Supported Types
Just as every CombineUI binding may be used with subclasses of its supported type, the `UIView` bindings are compatible with all `UIView` subclasses.
### Code Example
```swift
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)
```
## `UIViewController`
### Property Wrapper
```swift
@ViewController // Projected Value: ViewControllerInterface
```
`ViewControllerInterface`
```swift
var isVisible: AnyPublisher
var viewWillAppear: AnyPublisher
var viewDidAppear: AnyPublisher
var viewWillDisappear: AnyPublisher
var viewDidDisappear: AnyPublisher
```
### Extension Method
```swift
func lifecyclePublisher() -> AnyPublisher
```
### Publisher Extension
```swift
func isVisiblePublisher() -> AnyPublisher where Output == ViewControllerLifecycleEvent
```
### Code Example
```swift
// 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)
```
### Notes
- Every subscription adds a helper view (of zero size) to the view hierarchy using view controller containment, therefore consider [sharing the subscription](https://developer.apple.com/documentation/combine/publisher/share()) when there are multiple subscribers (as demonstrated in the example above).
- In stark contrast to the other property wrappers, the `@ViewController` property wrapper is considered to be less useful than the extension methods. This is because the extension methods may be used within a view controller instance to subscribe to its own lifecycle events, for example:
```swift
override func viewDidLoad() {
super.viewDidLoad()
lifecyclePublisher()
.isVisiblePublisher()
.sink { isVisible in }
.store(in: &cancellables)
}
```
## Caveats
### Delegation
CombineUI provides publishers for common delegate protocol methods, however due to the nature of publishers, delegate methods that have return values are not available as publishers. Furthermore, the delegate method publishers provided by CombineUI are available as a convenience only. For complex setups, or even when more than just a few delegate methods are required, it is recommended to use an actual delegate class instance.
Note too that setting a delegate property will disable the delegate publisher(s). This means it is not possible to use an actual delegate class instance along with the delegate publishers. Therefore select one pattern or the other for each specific use case.
## Customization
### Adding Bindings to Views and Controls
Additional bindings are easily added to existing views and controls. This is useful for properties that CombineUI does not yet support natively.
Example:
```swift
extension Bindable where Target: UIView {
var tag: Binding {
Binding(self, for: \.tag)
}
}
```
### Adopting CombineUI in Custom Views and Controls
The same type of APIs that CombineUI provides can also be adopted by custom views and controls.
Example:
```swift
@propertyWrapper
struct Example {
var wrappedValue: T
var projectedValue: AnyPublisher
init(wrappedValue: T) {
self.wrappedValue = wrappedValue
self.projectedValue = wrappedValue
.valuePublisher()
.share()
.eraseToAnyPublisher()
}
}
extension Bindable where Target: ExampleControl {
var value: Binding {
Binding(self, for: \.value)
}
}
extension ExampleControl {
func valuePublisher() -> AnyPublisher {
publisher(for: .valueChanged)
.compactMap { [weak self] _ in self?.value }
.prepend(value)
.eraseToAnyPublisher()
}
}
```
The CombineUI source code may be used as reference for additional examples.
## Contributing
While interest in contributing to this project is appreciated, it has been open
sourced solely for the purpose of sharing with the community. This means we are
unable to accept outside contributions at this time and pull requests will not
be reviewed or merged. To report a security concern or vulnerability, please
submit a GitHub issue.
## License
Licensed under the [Match Group Modified 3-Clause BSD License](
https://github.com/Tinder/CombineUI/blob/main/LICENSE
).