I have a parentViewController that contains a childViewController in it. The childViewController has a UICollectionView. I'd like to observe the scroll and have it conform to another protocol to speak to the parentViewController. I can get this to work for the scroll if I call the method in the UICollectionViewDelegate, but I want to be able to have it across any generic ViewController that conforms to the delegate that has a scrollView inside of it. Is this possible?
Code Below:
protocol HomeScrollDelegate: UIScrollViewDelegate {
func showButtons()
func hideButtons()
weak var homeScrollDelegate: HomeScrollDelegate? { get set }
}
extension HomeScrollDelegate {
func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if targetContentOffset.memory.y > scrollView.contentOffset.y {
homeScrollDelegate?.hideButtons()
} else {
homeScrollDelegate?.showButtons()
}
}
}
class HomeCollectionViewController: UIViewController {
weak var homeDelegate: HomeScrollDelegate?
etc etc
}
extension HomeCollectionViewController: UICollectionViewDelegate {
weak var homeScrollDelegate: HomeScrollDelegate? {
get {
return self.homeScrollDelegate
}
set {
self.homeScrollDelegate = homeDelegate
}
}
func showButtons() {}
func hideButtons() {}
}
}
ParentViewController
// set childViewController.homeDelegate = self when creating children
extension ParentViewController: HomeScrollDelegate {
weak var homeScrollDelegate: HomeScrollDelegate? {
get {
return self.homeScrollDelegate
}
set {
if let homeController = UIViewController() as? HomeCollectionViewController {
self.homeScrollDelegate = homeController.homeDelegate
}
}
}
func showButtons() {
// show buttons
}
func hideButtons() {
// hide Buttons
}
}
None of this gets called. I feel like I'm doing something terribly incorrect.
Below is code that works great, but I want something slightly more generic. I know I can easily set up a protocol and just have any viewcontroller that needs to, conform to it like so:
protocol HomeScrollDelegate {
func showButtons()
func hideButtons()
}
class HomeCollectionViewController: UIViewController {
weak var homeDelegate: HomeScrollDelegate?
etc etc
}
extension HomeCollectionViewController: UICollectionViewDelegate {
func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if targetContentOffset.memory.y > scrollView.contentOffset.y {
homeScrollDelegate?.hideButtons()
} else {
homeScrollDelegate?.showButtons()
}
}
}
ParentViewController
extension ParentViewController: HomeScrollDelegate {
func showButtons() {
// show buttons
}
func hideButtons() {
// hide Buttons
}
}
My goal in doing this is so that I don't have to override scrollViewWillEndDragging(_:_:_:) in every UIScrollView delegate because I will have other childViewControllers that are not UICollectionViews but will have scrollViews.
Related
The set-up of my app is as follows:
RootVC, which has a counter UILabel
A subContainer VC, "FirstVC" embedded within a UIPageViewController, which takes up the lower half of RootVC. FirstVC has a button, pressing which increases the UILabel on RootVC by one.
Now I have used a closure to communicate between the 2 ViewControllers, which works fine initially, but when I try to add two more ViewControllers (SecondVC and ThirdVC) both of which are absolutely identical to the FirstVC, and have a button each meant to do the same thing (closure callback included), this throws an error for some reason, which I can't understand
The FirstVC (and the SecondVC & ThirdVC) is as follows:
class FirstVC {
var buttonCallback: () -> Void = { }
#objc func buttonPressed(_ sender: UIButton) {
buttonCallback()
}
}
My RootVC:
class RootVC: UIViewController {
var tappedCount: Int = 0 {
didSet {
label.text = "\(tappedCount)"
}
}
override func viewDidLoad() {
super.viewDidLoad()
let pageController = PageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)
pageController.buttonCallback = { self.tappedCount += 1 }
}
}
And my UIPageViewController class is as follows:
class PageViewController: UIPageViewController {
var subViewControllers = [FirstSubVC(), SecondSubVC(), ThirdVC()] {
didSet {
// just in case the controllers might change later on
subViewControllers.forEach { $0.buttonCallback = buttonCallback } ////ERROR: Value of type 'UIViewController' has no member 'buttonCallback'////
}
}
var buttonCallback: () -> Void = { } {
didSet {
subViewControllers.forEach { $0.buttonCallback = buttonCallback } ////ERROR: Value of type 'UIViewController' has no member 'buttonCallback'////
}
}
extension PageViewController: UIPageViewControllerDelegate, UIPageViewControllerDataSource {
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return subViewControllers.count
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let currentIndex:Int = subViewControllers.firstIndex(of: viewController as! FirstVC) ?? 0
if currentIndex <= 0 {
return nil
}
return subViewControllers[currentIndex-1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let currentIndex:Int = subViewControllers.firstIndex(of: viewController as! FirstVC) ?? 0
if currentIndex >= subViewControllers.count-1 {
return nil
}
return subViewControllers[currentIndex+1]
}
}
I have tried to not cast the viewController as a FirstVC in the UIPageViewController extension but this creates another issue, and I don't believe this is the right way to solve this issue.
EDIT
protocol CallableThing {
var buttonCallback: () -> Void = { } //ERROR Property in protocol must have explicit { get } or { get set } specifier
}
Your code cannot work. You have to identify the controller by a property, either use the title property or adopt the Identifiable protocol
For example
class FirstVC : UIViewController {
func viewDidLoad() {
super.viewDidLoad()
title = "First"
}
var buttonCallback: () -> Void = { }
#objc func buttonPressed(_ sender: UIButton) {
buttonCallback()
}
}
extension PageViewController: UIPageViewControllerDelegate, UIPageViewControllerDataSource {
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return subViewControllers.count
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let currentIndex = subViewControllers.firstIndex(where: {$0.title == viewController.title}), currentIndex > 0 else { return nil }
return subViewControllers[currentIndex-1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let currentIndex = subViewControllers.firstIndex(where: {$0.title == viewController.title}), currentIndex <= subViewControllers.count-1 else { return nil }
return subViewControllers[currentIndex+1]
}
}
Nothing to do with closures, nothing to do with communication. It's just a matter of types.
If you say, for example:
var subViewControllers = [FirstSubVC()]
Then subViewControllers is implicitly typed as [FirstSubVC] — an array of FirstSubVC. So every member of that array will be a FirstSubVC. So if FirstSubVC has a buttonCallback property, or any known property, then the compiler knows that every member of that array has one, and it is legal to say
subViewControllers.forEach { $0.buttonCallback = buttonCallback }
...because $0 is a FirstSubVC, which has a buttonCallback, as we just established.
But if you say
var subViewControllers = [FirstSubVC(), SecondSubVC(), ThirdVC()]
Now what are all of those? What are they in common typologically? The compiler can conclude only that they are all UIViewController subclasses. So subViewControllers is typed implicitly as [UIViewController]. It's now just an array of plain vanilla UIViewControllers. That's all the compiler now knows about this array.
So now it is illegal to say
subViewControllers.forEach { $0.buttonCallback = buttonCallback }
because now $0 is a UIViewController and no more, and a plain UIViewController has no buttonCallback property!
So you have two choices. The simplest, if they are really all identical, is to set up a common superclass. Let's say it's MyCallableVC, a UIViewController subclass. Give MyCallableVC a buttonCallback property. And make FirstSubVC, SecondSubVC, and ThirdSubVC subclasses of MyCallableVC:
class MyCallableVC : UIViewController {
var buttonCallback: () -> Void = { }
}
class FirstSubVC : MyCallableVC {}
class SecondSubVC : MyCallableVC {}
class ThirdVC : MyCallableVC {}
So now when you say
var subViewControllers = [FirstSubVC(), SecondSubVC(), ThirdVC()]
subViewControllers is implicitly a [MyCallableVC] because that's what they all have in common. And if not, you can make it so:
var subViewControllers : MyCallableVC = [FirstSubVC(), SecondSubVC(), ThirdVC()]
Now your expression $0.buttonCallback is legal! $0 is a MyCallableVC, which has a buttonCallback property.
The other possibility is to use a noninheritance type that they all have in common. That's called a protocol. You declare a protocol, let's say CallableThing, that requires a buttonCallback property. You make FirstSubVC, SecondSubVC, and ThirdVC adopt that protocol:
protocol CallableThing : UIViewController {
var buttonCallback: () -> Void { get set }
}
class FirstSubVC : CallableThing {
var buttonCallback: () -> Void = { }
}
class SecondSubVC : CallableThing {
var buttonCallback: () -> Void = { }
}
class ThirdVC : CallableThing {
var buttonCallback: () -> Void = { }
}
And you explicitly type your array as an array of adopters of that protocol:
var subViewControllers : CallableThing = [FirstSubVC(), SecondSubVC(), ThirdVC()]
Now once again your expression $0.buttonCallback is legal, because now $0 is a CallableThing, which by definition has a buttonCallback property.
It worked before because you have been force unwrapping a subViewcontrollers to FirstVC class when there was a single FirstVC class object. After adding additional controllers which were objects of classes SecondVC and ThirdVC, force unwrapping in pageViewController(:viewControllerBefore:) and
pageViewController(:viewControllerBefore:) leads to an error.
Solution
classes FirstVC, SecondVC and ThirdVC are subclasses of UIViewController and the code should reflect that fact.
Change this line of code
let currentIndex:Int = subViewControllers.firstIndex(of: viewController as! FirstVC) ?? 0
to
let currentIndex:Int = subViewControllers.firstIndex(of: viewController) ?? 0
class PageViewController: UIPageViewController {
var subViewControllers = [FirstSubVC(), SecondSubVC(), ThirdVC()] {
didSet {
// just in case the controllers might change later on
subViewControllers.forEach { $0.buttonCallback = buttonCallback } ////ERROR: Value of type 'UIViewController' has no member 'buttonCallback'////
}
}
var buttonCallback: () -> Void = { } {
didSet {
subViewControllers.forEach { $0.buttonCallback = buttonCallback } ////ERROR: Value of type 'UIViewController' has no member 'buttonCallback'////
}
}
extension PageViewController: UIPageViewControllerDelegate, UIPageViewControllerDataSource {
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return subViewControllers.count
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let currentIndex:Int = subViewControllers.firstIndex(of: viewController as! UIViewController) ?? 0
if currentIndex <= 0 {
return nil
}
return subViewControllers[currentIndex-1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let currentIndex:Int = subViewControllers.firstIndex(of: viewController as! UIViewController) ?? 0
if currentIndex >= subViewControllers.count-1 {
return nil
}
return subViewControllers[currentIndex+1]
}
}
Improvements
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let currentIndex = subViewControllers.firstIndex(of: viewController),
currentIndex > 0
else {
return nil
}
return subViewControllers[ currentIndex - 1]
}
When creating a UIViewControllerRepresentable for SwiftUI, how do you create a Coordinator so that it can access the delegate of a third party library?
In this case, I am trying to access BBMetal, a photo-filtering library.
This is a truncated version of the code we are trying to 'bridge' to SwiftUI:
class CameraPhotoFilterVC: UIViewController {
private var camera: BBMetalCamera!
private var metalView: BBMetalView!
private var faceView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
...
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
camera.start()
}...
}
extension CameraPhotoFilterVC: BBMetalCameraPhotoDelegate {
func camera(_ camera: BBMetalCamera, didOutput texture: MTLTexture) {
// do something with the photo
}
func camera(_ camera: BBMetalCamera, didFail error: Error) {
// In main thread
print("Fail taking photo. Error: \(error)")
}
}
Using UIViewRepresentable everything sets up properly and the CameraPhotoFilterVC works, starts up the camera, etc, but the extension does not respond. We tried to set this up as a Coordinator:
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<CameraPreviewView>) -> CameraViewController {
let cameraViewController = CameraViewController()
// Throws an error because what we really want is a BBMetalCameraPhotoDelegate
//cameraViewController.delegate = context.coordinator
return cameraViewController
}
class Coordinator: NSObject, BBMetalCameraPhotoDelegate {
var parent: CameraPreviewView
init(_ parent: CameraPreviewView) {
self.parent = parent
}
func camera(_ camera: BBMetalCamera, didOutput texture: MTLTexture) {
print("do something with the photo")
}
func camera(_ camera: BBMetalCamera, didFail error: Error) {
print("Fail taking photo. Error: \(error)")
}
}
We also tried simply leaving an extension of the ViewController:
final class CameraViewController : UIViewController {
...
}
extension CameraViewController: BBMetalCameraPhotoDelegate {
func camera(_ camera: BBMetalCamera, didOutput texture: MTLTexture) {
...
}
However the delegate methods from BBMetalCameraPhotoDelegate do not 'fire.
I suppose the question is: in UIViewControllerRepresentable or UIViewRepresentable, how do you add an "external" delegate in the makeUIViewController method?
Usually, if this was say a UIPickerView, the following line would work:
picker.delegate = context.coordinator
But in this case the delegate is 'once removed'
You need to set the BBMetalCamera's delegate at some point before you use it.
You might do it immediately after creating it. You didn't show how you create it, so I don't know if that would be a good place to set it.
You could probably just do it in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
camera.photoDelegate = self
}
in my view controller, i have set up like this.
protocol MenuDelegate {
func updateIndexOfMenuExpanded(index: Bool)
}
class ViewController: UIViewController,UITableViewDataSource, UITableViewDelegate {
var delegate : MenuDelegate?
func performaction() -> Void{
delegate!.updateIndexOfMenuExpanded(false)
}
}
and in my baseviewcontroller
class BaseViewController: UIViewController, MenuDelegate{
func updateIndexOfMenuExpanded(index: Bool){
self.menuIsExpanded = index
}
}
please help. thank you.
You have to set the delegate first.
let viewController = ViewController()
let baseViewController = BaseViewController()
viewController.delegate = baseViewController
It would also be wise to make the delegate a weak reference and to not force unwrap with !.
class ViewController: UIViewController,UITableViewDataSource, UITableViewDelegate {
weak var delegate : MenuDelegate?
func performaction() {
delegate?.updateIndexOfMenuExpanded(false)
}
}
Delegate is used when you want to pass data between viewcontrollers.this aproach is one to one
Here is the answer how to pass data using delegate
in viewcontroller
define protocol in view controller
protocol ViewController1BackClicked {
func btnBackClicked(str : String)
}
class ViewController1: UIViewController {
var strTitle : String?
var delegate : ViewController1BackClicked?
override func viewDidLoad() {
super.viewDidLoad()
if strTitle != nil{
title = strTitle
}
}
override func viewWillDisappear(animated: Bool) {
delegate?.btnBackClicked("Krutarth")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
now Protocol is created.to pass data in another view controller
viewcontroller 1 we want to access data
func btnBackClicked(str: String) {
title = str
}
output : Krutarth
this is example how to use protocol
I'm fairly new at iOS programming. I have this setup:
ViewController view on IB, with class ViewController
SecondController view on IB, with class secondController
I have protocol:
protocol SecondControllerDelegate {
func getSomething() -> String
}
and I have delegate variable on SecondController:
class secondController: UIViewController {
var delegate: SecondControllerDelegate?
#IBOutlet weak var labelStatus: UILabel!
override func ViewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonTouch(sender: AnyObject) {
labelStatus.text = delegate?.getSomething()
}
func try () {
labelStatus.text = "testing"
}
}
Now, according to the hints everywhere, in order so I can call delegate?.getSomething() at SecondController.buttonTouch(), I need to set like this on viewController:
class ViewController: UIViewController, SecondControllerDelegate {
override func viewDidLoad () {
super.viewDidLoad()
SecondController.delegate = self
}
func doSomething () -> String {
return "testing"
}
}
But this generates error 'SecondController.type' does not have a member named 'delegate'.
Some other websites say:
class ViewController: UIViewController, SecondControllerDelegate {
var secondController = SecondController()
override func viewDidLoad () {
super.viewDidLoad()
secondController.delegate = self
}
func doSomething () -> String {
return "testing"
}
}
With this, there are no error. But if I do something on the second screen that should call the delegate, it doesn't call the delegate, like the SecondController is two different objects (one is created by StoryBoard, one is created manually within the ViewController), i.e. the labelStatus that should have changed to "testing", doesn't change at all. But it changes if function try() is called. How am I supposed to do this?
EDIT: I forgot to mention that I used NavigationController, and segue to transition from first screen to second screen.
Because you try to learn how to build a delegate in Swift, I have written you a plain delegate example below
protocol SecondViewControllerDelegate {
func didReceiveInformationFromSecondViewcontroller (information: String)
}
class ViewController: UIViewController, SecondViewControllerDelegate {
func openSecondViewController () {
if let secondViewControllerInstance: SecondViewController = storyboard?.instantiateViewControllerWithIdentifier("SecondViewController") as? SecondViewController {
secondViewControllerInstance.delegate = self
navigationController?.pushViewController(secondViewControllerInstance, animated: true)
}
}
func didReceiveInformationFromSecondViewcontroller(information: String) {
////Here you get the information, after sendInfoToViewController() has been executed
}
}
class SecondViewController: UIViewController {
var delegate: SecondViewControllerDelegate?
func sendInfoToViewController () {
delegate?.didReceiveInformationFromSecondViewcontroller("This ist the information")
}
}
UPDATE
Following the same thing in using Storyboard Segues
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let secondViewControllerInstance: SecondViewController = segue.destinationViewController as? SecondViewController {
secondViewControllerInstance.delegate = self
}
}
I'm subclassing UIScrollView to add some features such as double tap to zoom and an image property for gallery purposes. But in order to do the image part my subclass has to be its own delegate and implement the viewForZoomingInScrollView.
But then when someone uses my scroll view subclass, they might like to get delegate notifications as well to see scrollViewDidScroll or what have you.
In Swift, how do I get both of these?
Here is a Swift version of this pattern:
Although forwardInvocation: is disabled in Swift, we can still use forwardingTargetForSelector:
class MyScrollView: UIScrollView {
class _DelegateProxy: NSObject, UIScrollViewDelegate {
weak var _userDelegate: UIScrollViewDelegate?
override func respondsToSelector(aSelector: Selector) -> Bool {
return super.respondsToSelector(aSelector) || _userDelegate?.respondsToSelector(aSelector) == true
}
override func forwardingTargetForSelector(aSelector: Selector) -> AnyObject? {
if _userDelegate?.respondsToSelector(aSelector) == true {
return _userDelegate
}
else {
return super.forwardingTargetForSelector(aSelector)
}
}
func viewForZoomingInScrollView(scrollView: MyScrollView) -> UIView? {
return scrollView.viewForZooming()
}
// Just a demo. You don't need this.
func scrollViewDidScroll(scrollView: MyScrollView) {
scrollView.didScroll()
_userDelegate?.scrollViewDidScroll?(scrollView)
}
}
private var _delegateProxy = _DelegateProxy()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
super.delegate = _delegateProxy
}
override init(frame: CGRect) {
super.init(frame: frame)
super.delegate = _delegateProxy
}
override var delegate:UIScrollViewDelegate? {
get {
return _delegateProxy._userDelegate
}
set {
self._delegateProxy._userDelegate = newValue;
/* It seems, we don't need this anymore.
super.delegate = nil
super.delegate = _delegateProxy
*/
}
}
func viewForZooming() -> UIView? {
println("self viewForZooming")
return self.subviews.first as? UIView // whatever
}
func didScroll() {
println("self didScroll")
}
}
Here's a simple working Playground version in Swift 3 that acts purely as an observer rather than only as an interceptor like the other answers here.
The distinction is that the original scroll view delegate should have all of its delegate methods called like normal versus them being hijacked by another delegate.
(You can copy/paste this into a playground and run it to test)
import UIKit
final class ScrollViewObserver: NSObject, UIScrollViewDelegate {
// MARK: - Instantiation
init(scrollView: UIScrollView) {
super.init()
self.scrollView = scrollView
self.originalScrollDelegate = scrollView.delegate
scrollView.delegate = self
}
deinit {
self.remove()
}
// MARK: - API
/// Removes ourselves as an observer, resetting the scroll view's original delegate
func remove() {
self.scrollView?.delegate = self.originalScrollDelegate
}
// MARK: - Private Properties
fileprivate weak var scrollView: UIScrollView?
fileprivate weak var originalScrollDelegate: UIScrollViewDelegate?
// MARK: - Forwarding Delegates
/// Note: we forward all delegate calls here since Swift does not support forwardInvocation: or NSProxy
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// Run any custom logic or send any notifications here
print("proxy did scroll")
// Then, forward the call to the original delegate
self.originalScrollDelegate?.scrollViewDidScroll?(scrollView)
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewDidZoom?(scrollView)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewWillBeginDragging?(scrollView)
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
self.originalScrollDelegate?.scrollViewWillEndDragging?(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
self.originalScrollDelegate?.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate)
}
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewWillBeginDecelerating?(scrollView)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewDidEndDecelerating?(scrollView)
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewDidEndScrollingAnimation?(scrollView)
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.originalScrollDelegate?.viewForZooming?(in: scrollView)
}
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
self.originalScrollDelegate?.scrollViewWillBeginZooming?(scrollView, with: view)
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
self.originalScrollDelegate?.scrollViewDidEndZooming?(scrollView, with: view, atScale: scale)
}
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
return self.originalScrollDelegate?.scrollViewShouldScrollToTop?(scrollView) == true
}
func scrollViewDidScrollToTop(_ scrollView: UIScrollView) {
self.originalScrollDelegate?.scrollViewDidScrollToTop?(scrollView)
}
}
final class TestView: UIView, UIScrollViewDelegate {
let scrollView = UIScrollView()
fileprivate(set) var scrollObserver: ScrollViewObserver?
required init() {
super.init(frame: .zero)
self.scrollView.delegate = self
self.scrollObserver = ScrollViewObserver(scrollView: self.scrollView)
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("view's original did scroll delegate method called")
}
}
let testView = TestView()
testView.scrollView.setContentOffset(CGPoint(x: 0, y: 100), animated: true)
testView.scrollObserver?.remove()
print("removed the observer")
testView.scrollView.setContentOffset(CGPoint(x: 0, y: 200), animated: true)
testView.scrollView.setContentOffset(CGPoint(x: 0, y: 300), animated: true)
This prints
proxy did scroll
view's original did scroll delegate method called
removed the observer
view's original did scroll delegate method called
view's original did scroll delegate method called
Swift 4+ version of rintaro's excellent answer:
class MyScrollView: UIScrollView {
class _DelegateProxy: NSObject, UIScrollViewDelegate {
weak var _userDelegate: UIScrollViewDelegate?
override func responds(to aSelector: Selector!) -> Bool {
return super.responds(to: aSelector) || _userDelegate?.responds(to: aSelector) == true
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
if _userDelegate?.responds(to: aSelector) == true {
return _userDelegate
}
return super.forwardingTarget(for: aSelector)
}
//This function is just a demonstration, it can be replaced/removed.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
(scrollView as? MyScrollView)?.didScroll()
_userDelegate?.scrollViewDidScroll?(scrollView)
}
}
fileprivate let _delegateProxy = _DelegateProxy()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
super.delegate = _delegateProxy
}
override init(frame: CGRect) {
super.init(frame: frame)
super.delegate = _delegateProxy
}
override var delegate: UIScrollViewDelegate? {
get {
return _delegateProxy._userDelegate
}
set {
_delegateProxy._userDelegate = newValue
}
}
func didScroll() {
print("didScroll")
}
}
I don't know about any 100% Swift solution for this.
Taking this ObjC answer to the same problem, and trying to port it to Swift it turns out that is not possible since NSInvocation is not available in Swift.
What we can do is to implement the suggested MyScrollViewPrivateDelegate in ObjC(don't forget to import it in the bridging header file) and the scroll view subclass in Swift like the following:
MyScrollView.swift
import UIKit
class MyScrollView: UIScrollView {
private let myDelegate = MyScrollViewPrivateDelegate()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
super.delegate = myDelegate
}
override init(frame: CGRect) {
super.init(frame: frame)
super.delegate = myDelegate
}
override var delegate: UIScrollViewDelegate? {
set {
myDelegate.userDelegate = newValue
super.delegate = nil
super.delegate = myDelegate
}
get {
return myDelegate.userDelegate
}
}
func viewForZooming() -> UIView {
return UIView()// return whatever you want here...
}
}
MyScrollViewPrivateDelegate.h
#import <UIKit/UIKit.h>
#interface MyScrollViewPrivateDelegate : NSObject <UIScrollViewDelegate>
#property (weak, nonatomic) id<UIScrollViewDelegate> userDelegate;
#end
MyScrollViewPrivateDelegate.m
#import "MyScrollViewPrivateDelegate.h"
#import "YOUR_MODULE-Swift.h"
#implementation MyScrollViewPrivateDelegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
// you could check if the user delegate responds to viewForZoomingInScrollView and call it instead...
return [(MyScrollView *)scrollView viewForZooming];
}
- (BOOL)respondsToSelector:(SEL)selector
{
return [_userDelegate respondsToSelector:selector] || [super respondsToSelector:selector];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:_userDelegate];
}
#end