Skip to content
2 changes: 1 addition & 1 deletion Sources/CombineCocoa/Controls/UIButton+Combine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import UIKit
@available(iOS 13.0, *)
public extension UIButton {
/// A publisher emitting tap events from this button.
var tapPublisher: AnyPublisher<Void, Never> {
var clickPublisher: AnyPublisher<Void, Never> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

controlEventPublisher(for: .touchUpInside)
}
}
Expand Down
147 changes: 144 additions & 3 deletions Sources/CombineCocoa/Controls/UICollectionView+Combine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import Combine
public extension UICollectionView {
/// Combine wrapper for `collectionView(_:didSelectItemAt:)`
var didSelectItemPublisher: AnyPublisher<IndexPath, Never> {
guard let delegateProxy = innerDelegateWrap?.proxy else {
return Empty<IndexPath, Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UICollectionViewDelegate.collectionView(_:didSelectItemAt:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { $0[1] as! IndexPath }
Expand All @@ -24,6 +29,11 @@ public extension UICollectionView {

/// Combine wrapper for `collectionView(_:didDeselectItemAt:)`
var didDeselectItemPublisher: AnyPublisher<IndexPath, Never> {
guard let delegateProxy = innerDelegateWrap?.proxy else {
return Empty<IndexPath, Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UICollectionViewDelegate.collectionView(_:didDeselectItemAt:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { $0[1] as! IndexPath }
Expand All @@ -32,6 +42,11 @@ public extension UICollectionView {

/// Combine wrapper for `collectionView(_:didHighlightItemAt:)`
var didHighlightItemPublisher: AnyPublisher<IndexPath, Never> {
guard let delegateProxy = innerDelegateWrap?.proxy else {
return Empty<IndexPath, Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UICollectionViewDelegate.collectionView(_:didHighlightItemAt:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { $0[1] as! IndexPath }
Expand All @@ -40,6 +55,11 @@ public extension UICollectionView {

/// Combine wrapper for `collectionView(_:didUnhighlightItemAt:)`
var didUnhighlightRowPublisher: AnyPublisher<IndexPath, Never> {
guard let delegateProxy = innerDelegateWrap?.proxy else {
return Empty<IndexPath, Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UICollectionViewDelegate.collectionView(_:didUnhighlightItemAt:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { $0[1] as! IndexPath }
Expand All @@ -48,6 +68,11 @@ public extension UICollectionView {

/// Combine wrapper for `collectionView(_:willDisplay:forItemAt:)`
var willDisplayCellPublisher: AnyPublisher<(cell: UICollectionViewCell, indexPath: IndexPath), Never> {
guard let delegateProxy = innerDelegateWrap?.proxy else {
return Empty<(cell: UICollectionViewCell, indexPath: IndexPath), Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UICollectionViewDelegate.collectionView(_:willDisplay:forItemAt:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { ($0[1] as! UICollectionViewCell, $0[2] as! IndexPath) }
Expand All @@ -56,6 +81,11 @@ public extension UICollectionView {

/// Combine wrapper for `collectionView(_:willDisplaySupplementaryView:forElementKind:at:)`
var willDisplaySupplementaryViewPublisher: AnyPublisher<(supplementaryView: UICollectionReusableView, elementKind: String, indexPath: IndexPath), Never> {
guard let delegateProxy = innerDelegateWrap?.proxy else {
return Empty<(supplementaryView: UICollectionReusableView, elementKind: String, indexPath: IndexPath), Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UICollectionViewDelegate.collectionView(_:willDisplaySupplementaryView:forElementKind:at:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { ($0[1] as! UICollectionReusableView, $0[2] as! String, $0[3] as! IndexPath) }
Expand All @@ -64,6 +94,11 @@ public extension UICollectionView {

/// Combine wrapper for `collectionView(_:didEndDisplaying:forItemAt:)`
var didEndDisplayingCellPublisher: AnyPublisher<(cell: UICollectionViewCell, indexPath: IndexPath), Never> {
guard let delegateProxy = innerDelegateWrap?.proxy else {
return Empty<(cell: UICollectionViewCell, indexPath: IndexPath), Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UICollectionViewDelegate.collectionView(_:didEndDisplaying:forItemAt:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { ($0[1] as! UICollectionViewCell, $0[2] as! IndexPath) }
Expand All @@ -72,22 +107,128 @@ public extension UICollectionView {

/// Combine wrapper for `collectionView(_:didEndDisplayingSupplementaryView:forElementKind:at:)`
var didEndDisplaySupplementaryViewPublisher: AnyPublisher<(supplementaryView: UICollectionReusableView, elementKind: String, indexPath: IndexPath), Never> {
guard let delegateProxy = innerDelegateWrap?.proxy else {
return Empty<(supplementaryView: UICollectionReusableView, elementKind: String, indexPath: IndexPath), Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UICollectionViewDelegate.collectionView(_:didEndDisplayingSupplementaryView:forElementOfKind:at:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { ($0[1] as! UICollectionReusableView, $0[2] as! String, $0[3] as! IndexPath) }
.eraseToAnyPublisher()
}

@discardableResult
func setCellSize(_ callback: @escaping (_ indexPath: IndexPath) -> CGSize) -> Self {
innerDelegateWrap?.proxy?.cellSizeCallback = callback
return self
}

@discardableResult
func setInset(_ callback: @escaping (_ section: Int) -> UIEdgeInsets) -> Self {
innerDelegateWrap?.proxy?.insetCallback = callback
return self
}

@discardableResult
func setMinimumLineSpacing(_ callback: @escaping (_ section: Int) -> CGFloat) -> Self {
innerDelegateWrap?.proxy?.minimumLineSpacingCallback = callback
return self
}

@discardableResult
func setMinimumInteritemSpacing(_ callback: @escaping (_ section: Int) -> CGFloat) -> Self {
innerDelegateWrap?.proxy?.minimumInteritemSpacingCallback = callback
return self
}

@discardableResult
func setHeaderSize(_ callback: @escaping (_ section: Int) -> CGSize) -> Self {
innerDelegateWrap?.proxy?.headerSizeCallback = callback
return self
}

@discardableResult
func setFooterSize(_ callback: @escaping (_ section: Int) -> CGSize) -> Self {
innerDelegateWrap?.proxy?.footerSizeCallback = callback
return self
}

override var delegateProxy: DelegateProxy {
CollectionViewDelegateProxy.createDelegateProxy(for: self)
func useInnerDelegate() {
innerDelegateWrap = .init()
innerDelegateWrap?.proxy = CollectionViewDelegateProxy.createDelegateProxy(for: self)
innerDelegateWrap?.proxy?.wrap = innerDelegateWrap
innerDelegateWrap?.collectionView = self
}

func removeInnerDelegate() {
innerDelegateWrap = nil
}

fileprivate var innerFlowLayout: UICollectionViewFlowLayout? {
collectionViewLayout as? UICollectionViewFlowLayout
}

static var innerDelegateWrapKey: Void?
private var innerDelegateWrap: CollectionViewDelegateWrap? {
get { objc_getAssociatedObject(self, &UICollectionView.innerDelegateWrapKey) as? CollectionViewDelegateWrap }
set { objc_setAssociatedObject(self, &UICollectionView.innerDelegateWrapKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}

override public var delegateProxy: DelegateProxy? {
innerDelegateWrap?.proxy
}

}

@available(iOS 13.0, *)
private class CollectionViewDelegateProxy: DelegateProxy, UICollectionViewDelegate, DelegateProxyType {
fileprivate class CollectionViewDelegateWrap {

weak var proxy: CollectionViewDelegateProxy?
weak var collectionView: UICollectionView?

}

@available(iOS 13.0, *)
private class CollectionViewDelegateProxy: DelegateProxy, UICollectionViewDelegateFlowLayout, DelegateProxyType {
weak var wrap: CollectionViewDelegateWrap?

func setDelegate(to object: UICollectionView) {
object.delegate = self
}

var cellSizeCallback: ((_ indexPath: IndexPath) -> CGSize)?
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
cellSizeCallback?(indexPath) ?? wrap?.collectionView?.innerFlowLayout?.itemSize ?? CGSize(width: 0.1, height: 0.1)
}

var insetCallback: ((_ section: Int) -> UIEdgeInsets)?
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let result = insetCallback?(section) ?? wrap?.collectionView?.innerFlowLayout?.sectionInset ?? .zero
return result
}

var minimumLineSpacingCallback: ((_ section: Int) -> CGFloat)?
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
let result = minimumLineSpacingCallback?(section) ?? wrap?.collectionView?.innerFlowLayout?.minimumLineSpacing ?? 0
return max(result - 0.5, 0)
}

var minimumInteritemSpacingCallback: ((_ section: Int) -> CGFloat)?
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
let result = minimumInteritemSpacingCallback?(section) ?? wrap?.collectionView?.innerFlowLayout?.minimumInteritemSpacing ?? 0
return max(result - 0.5, 0)
}

var headerSizeCallback: ((_ section: Int) -> CGSize)?
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
headerSizeCallback?(section) ?? wrap?.collectionView?.innerFlowLayout?.headerReferenceSize ?? .zero
}

var footerSizeCallback: ((_ section: Int) -> CGSize)?
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
footerSizeCallback?(section) ?? wrap?.collectionView?.innerFlowLayout?.footerReferenceSize ?? .zero
}
}
#endif
// swiftlint:enable force_cast
58 changes: 56 additions & 2 deletions Sources/CombineCocoa/Controls/UIScrollView+Combine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
// Created by Joan Disho on 09/08/2019.
// Copyright © 2020 Combine Community. All rights reserved.
//

#if !(os(iOS) && (arch(i386) || arch(arm)))
import UIKit
import Combine
Expand Down Expand Up @@ -41,6 +40,11 @@ public extension UIScrollView {

/// Combine wrapper for `scrollViewDidScroll(_:)`
var didScrollPublisher: AnyPublisher<Void, Never> {
guard let delegateProxy = delegateProxy else {
return Empty<Void, Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UIScrollViewDelegate.scrollViewDidScroll(_:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { _ in () }
Expand All @@ -49,6 +53,11 @@ public extension UIScrollView {

/// Combine wrapper for `scrollViewWillBeginDecelerating(_:)`
var willBeginDeceleratingPublisher: AnyPublisher<Void, Never> {
guard let delegateProxy = delegateProxy else {
return Empty<Void, Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UIScrollViewDelegate.scrollViewWillBeginDecelerating(_:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { _ in () }
Expand All @@ -57,6 +66,11 @@ public extension UIScrollView {

/// Combine wrapper for `scrollViewDidEndDecelerating(_:)`
var didEndDeceleratingPublisher: AnyPublisher<Void, Never> {
guard let delegateProxy = delegateProxy else {
return Empty<Void, Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UIScrollViewDelegate.scrollViewDidEndDecelerating(_:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { _ in () }
Expand All @@ -65,6 +79,11 @@ public extension UIScrollView {

/// Combine wrapper for `scrollViewWillBeginDragging(_:)`
var willBeginDraggingPublisher: AnyPublisher<Void, Never> {
guard let delegateProxy = delegateProxy else {
return Empty<Void, Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UIScrollViewDelegate.scrollViewWillBeginDragging(_:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { _ in () }
Expand All @@ -73,6 +92,11 @@ public extension UIScrollView {

/// Combine wrapper for `scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)`
var willEndDraggingPublisher: AnyPublisher<(velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>), Never> {
guard let delegateProxy = delegateProxy else {
return Empty<(velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>), Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UIScrollViewDelegate.scrollViewWillEndDragging(_:withVelocity:targetContentOffset:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { values in
Expand All @@ -86,6 +110,11 @@ public extension UIScrollView {

/// Combine wrapper for `scrollViewDidEndDragging(_:willDecelerate:)`
var didEndDraggingPublisher: AnyPublisher<Bool, Never> {
guard let delegateProxy = delegateProxy else {
return Empty<Bool, Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UIScrollViewDelegate.scrollViewDidEndDragging(_:willDecelerate:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { $0[1] as! Bool }
Expand All @@ -94,6 +123,11 @@ public extension UIScrollView {

/// Combine wrapper for `scrollViewDidZoom(_:)`
var didZoomPublisher: AnyPublisher<Void, Never> {
guard let delegateProxy = delegateProxy else {
return Empty<Void, Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UIScrollViewDelegate.scrollViewDidZoom(_:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { _ in () }
Expand All @@ -102,6 +136,11 @@ public extension UIScrollView {

/// Combine wrapper for `scrollViewDidScrollToTop(_:)`
var didScrollToTopPublisher: AnyPublisher<Void, Never> {
guard let delegateProxy = delegateProxy else {
return Empty<Void, Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UIScrollViewDelegate.scrollViewDidScrollToTop(_:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { _ in () }
Expand All @@ -110,6 +149,11 @@ public extension UIScrollView {

/// Combine wrapper for `scrollViewDidEndScrollingAnimation(_:)`
var didEndScrollingAnimationPublisher: AnyPublisher<Void, Never> {
guard let delegateProxy = delegateProxy else {
return Empty<Void, Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UIScrollViewDelegate.scrollViewDidEndScrollingAnimation(_:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { _ in () }
Expand All @@ -118,6 +162,11 @@ public extension UIScrollView {

/// Combine wrapper for `scrollViewWillBeginZooming(_:with:)`
var willBeginZoomingPublisher: AnyPublisher<UIView?, Never> {
guard let delegateProxy = delegateProxy else {
return Empty<UIView?, Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UIScrollViewDelegate.scrollViewWillBeginZooming(_:with:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { $0[1] as! UIView? }
Expand All @@ -126,13 +175,18 @@ public extension UIScrollView {

/// Combine wrapper for `scrollViewDidEndZooming(_:with:atScale:)`
var didEndZooming: AnyPublisher<(view: UIView?, scale: CGFloat), Never> {
guard let delegateProxy = delegateProxy else {
return Empty<(view: UIView?, scale: CGFloat), Never>(completeImmediately: false)
.eraseToAnyPublisher()
}

let selector = #selector(UIScrollViewDelegate.scrollViewDidEndZooming(_:with:atScale:))
return delegateProxy.interceptSelectorPublisher(selector)
.map { ($0[1] as! UIView?, $0[2] as! CGFloat) }
.eraseToAnyPublisher()
}

@objc var delegateProxy: DelegateProxy {
@objc var delegateProxy: DelegateProxy? {
ScrollViewDelegateProxy.createDelegateProxy(for: self)
}
}
Expand Down
Loading