Swift - Invoke Custom Delegate Method - ios

I am using BMPlayer library and want to implement custom control, for which I have the following class which confirm to following protocol
#objc public protocol BMPlayerControlViewDelegate: class {
func controlView(controlView: BMPlayerControlView, didChooseDefition index: Int)
func controlView(controlView: BMPlayerControlView, didPressButton button: UIButton)
func controlView(controlView: BMPlayerControlView, slider: UISlider, onSliderEvent event: UIControlEvents)
#objc optional func controlView(controlView: BMPlayerControlView, didChangeVideoPlaybackRate rate: Float)
}
open class BMPlayerControlView: UIView {
open weak var delegate: BMPlayerControlViewDelegate?
open weak var player: BMPlayer?
// Removed rest of the code for clarity
open func onButtonPressed(_ button: UIButton) {
autoFadeOutControlViewWithAnimation()
if let type = ButtonType(rawValue: button.tag) {
switch type {
case .play, .replay:
if playerLastState == .playedToTheEnd {
hidePlayToTheEndView()
}
default:
break
}
}
delegate?.controlView(controlView: self, didPressButton: button)
}
}
I am extending BMPlayerControlView class to extend the control view using the following code.
class BMPlayerCustomControlStyle3: BMPlayerControlView {
}
class BMPlayerStyle3: BMPlayer {
class override func storyBoardCustomControl() -> BMPlayerControlView? {
return BMPlayerCustomControlStyle3()
}
}
My question is, how do I invoke didPressButton delegate method? I don't want to overwrite onButtonPressed, I tried the following
extension BMPlayerCustomControlStyle3:BMPlayerControlViewDelegate {
func controlView(controlView: BMPlayerControlView, didChooseDefition index: Int) {
}
func controlView(controlView: BMPlayerControlView, didPressButton button: UIButton) {
print("Did Press Button Invoked")
}
func controlView(controlView: BMPlayerControlView, slider: UISlider, onSliderEvent event: UIControlEvents) {
}
}
And this doesn't seem to work, what am I missing here?
Thanks.

If you want your BMPlayerControlView subclass to also act as the delegate object, you need to set the delegate property as well (and conform to the BMPlayerControlViewDelegate protocol as you are already doing).
One way to do so is by overriding the delegate superclass property in your subclass:
class BMPlayerCustomControlStyle3: BMPlayerControlView {
override open weak var delegate: BMPlayerControlViewDelegate? {
get { return self }
set { /* fatalError("Delegate for internal use only!") */ }
}
}
Of course, when using the delegate internally such as this, you won't allow it to be used by BMPlayerControlView clients at all. The overridden set above ensures you get an error if trying to do so.

Related

Function not getting called with protocol delegate and view controller swift

I needed to delegate an click action for my UIView class to my UIViewController class since swift does not support multiple class inheritance. So i wanted it such that once a button is clicked on my subview, a function in my ViewController class is called. Am using protocol delegate to achieve this but on the click of my button it does not work for me as the function does not get called. Please help me out. Code snippet would be largely appreciated.
ViewController
var categoryItem: CategoryItem! = CategoryItem() //Category Item
private func setupExplore() {
//assign delegate of category item to controller
self.categoryItem.delegate = self
}
//function to be called
extension BrowseViewController: ExploreDelegate {
func categoryClicked(category: ProductCategory) {
print("clicked")
let categoryView = ProductByCategoryView()
categoryView.category = category
categoryView.modalPresentationStyle = .overCurrentContext
self.navigationController?.pushViewController(categoryView, animated: true)
}
}
Explore.swift (subview)
import UIKit
protocol ExploreDelegate:UIViewController {
func categoryClicked(category: ProductCategory)
}
class Explore: UIView {
var delegate: ExploreDelegate?
class CategoryItem: UIView {
var delegate: ExploreDelegate?
var category: ProductCategory? {
didSet {
self.configure()
}
}
var tapped: ((_ category: ProductCategory?) -> Void)?
func configure() {
self.layer.cornerRadius = 6
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.categoryTapped)))
self.layoutIfNeeded()
}
#objc func categoryTapped(_ sender: UIGestureRecognizer) {
delegate?.categoryClicked(category: ProductCategory.everything)
self.tapped?(self.category)
}
}

Delegate not executing after call swift

I have a viewController with another containerView insider set up to appear temporarily (added programmatically). The containerView is a sort of operation bar, which allows you to change values of the viewController. The protocol called from an IBAction of a button however, does not call the protocol set up inside the viewController class.
Here is the code from both classes:
class viewController: UIViewController, updateListDelegate {
let dataSource = containerView()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
func updateList(sender: containerView) {
print("is called") //is not printed
}
}
The code from the containerView:
protocol updateListDelegate {
func updateList(containerView)
}
class containerView: UIViewController {
var delegate: updateListDelegate?
#IBAction func AddSong(_ sender: UIButton) {
self.delegate?.updateList(sender: self)
}
}
If this method is only to be called from one object, then, in my opinion, I would not define a protocol. If multiple objects are to call this method, then I would define a protocol. This is typically how you would call a method backwards, using a basic delegate.
class ViewController: UIViewController {
let container = ContainerView()
override func viewDidLoad() {
super.viewDidLoad()
container.viewControllerDelegate = self
// push to this instance of container at some point
}
func doSomething() {
print("great success")
}
}
class ContainerView: UIViewController {
weak var viewControllerDelegate: ViewController?
#objc func someAction() {
if let viewControllerDelegate = viewControllerDelegate {
viewControllerDelegate.doSomething()
}
}
}
// prints "great success" when someAction() called
One of the most common mistakes people make is not keeping track of instances. For delegates to work, you must be sure you are using the specific instances that you've instantiated and assigned those delegates to.

Issue calling externally assigned closure from IBAction

I have this UIView subclass:
class EmptyAlbumsView: UIView {
class func instanceFromNib() -> EmptyAlbumsView {
return R.nib.emptyAlbumsView.firstView(owner: nil)! as EmptyAlbumsView
}
// MARK: - IBActions
#IBAction func didTapFreeAlbumButton(sender: UIButton) {
print("Tapping button")
print(didTapFreeAlbumButtonClosure)
didTapFreeAlbumButtonClosure?()
}
#IBAction func didTapEventAlbumButton(sender: UIButton) {
didTapEventAlbumButtonClosure?()
}
#IBAction func didTapWhatsTheDifferenceButton(sender: UIButton) {
didTapWhatsTheDifferenceButtonClosure?()
}
#IBAction func didTapSubmitAlbumCodeButton(sender: UIButton) {
didTapSubmitAlbumCodeButtonClosure?()
}
// MARK: - Public Properties
open var didTapFreeAlbumButtonClosure: EmptyClosure? {
didSet {
log.verbose("set to \(self.didTapFreeAlbumButtonClosure!)")
}
}
open var didTapEventAlbumButtonClosure: EmptyClosure?
open var didTapWhatsTheDifferenceButtonClosure: EmptyClosure?
open var didTapSubmitAlbumCodeButtonClosure: EmptyClosure?
}
which I am initialising in a UIViewController as such:
fileprivate func showEmptyAlbumsView() {
let emptyAlbumsView = EmptyAlbumsView.instanceFromNib()
emptyAlbumsView.didTapFreeAlbumButtonClosure = {
print("hey")
}
view.addSubview(emptyAlbumsView)
emptyAlbumsView.matchEdgeAnchorsOfView(self.view)
}
I can see the Tapping button log statement from IBAction, but the second log statement over there prints nil, as it appears the didTapFreeAlbumButtonClosure has not been set, and of course not getting called ever. I never see hey printed out.
However, in the didSet block I can clearly see the closure is getting set.
EmptyClosure is defined as typealias EmptyClosure = () -> ()
What am I missing here?
Ok, after a lot of frustration, the problem was rather silly.
In the Interface Builder I had set the File Owner of the my xib to EmptyAlbumsView. That was causing the IBActions to be called properly but when I tried hooking up some IBOutlets I was getting KVC errors.
It appears the proper way to do this is leave the File Owner class empty, and set the root views of the xib to EmptyAlbumsView. Then I'd hook my IBOutlets and IBActions directly to the view.

how can I call a method stored in a parent uiviewcontroller from the uiviewcontroller embedded in a container in that controller?

I have a UIViewController -let's call it parentViewController - and it contains a container. This container has embeddedViewController embedded in it.
Now, my parentViewController contains a method that prints something into the console:
func printSomeData(){
print("some data")
}
embeddedViewController has a button with action asigned to it:
#IBAction func printSomething(sender: AnyObject) {
is there a way that I could call method printSomeData from printSomething in Swift?
There are couple of ways such as by implementing delegate or by posting NSNotification. Here I show the sample delegation pattern. This is exact scenario as your own controller but from this you can get some concept and if you implement this hope this will accomplished your goal.
class ParentController:UIViewController,printing {
override func viewDidLoad() {
//
}
func presentEmbadedController(){
let embadedVC = EmbadedController()
embadedVC.delegate = self
}
func printSomeData() {
print("some date")
}
}
Here is the protocol something like this
protocol printing{
func printSomeData()
}
And then the EmbadedController like this
class EmbadedController:UIViewController {
var delegate: printing?
override func viewDidLoad() {
//
}
#IBAction func printSomething(sender: AnyObject) {
if let _ = delegate{
delegate?.printSomeData()
}
}
}
You can use NSNotificationCenter.defaultCenter().addObserver(...)

Adding Target-Action in Protocol Extension fails

I have a set of view controllers which will have a Menu bar button. I created a protocol for those viewControllers to adopt. Also, I've extended the protocol to add default functionalities.
My protocol looks like,
protocol CenterViewControllerProtocol: class {
var containerDelegate: ContainerViewControllerProtocol? { get set }
func setupMenuBarButton()
}
And, the extension looks like so,
extension CenterViewControllerProtocol where Self: UIViewController {
func setupMenuBarButton() {
let barButton = UIBarButtonItem(title: "Menu", style: .Done, target: self, action: "menuTapped")
navigationItem.leftBarButtonItem = barButton
}
func menuTapped() {
containerDelegate?.toggleSideMenu()
}
}
My viewController adopts the protocol -
class MapViewController: UIViewController, CenterViewControllerProtocol {
weak var containerDelegate: ContainerViewControllerProtocol?
override func viewDidLoad() {
super.viewDidLoad()
setupMenuBarButton()
}
}
I got the button to display nicely, but when I click on it, the app crashes with
[AppName.MapViewController menuTapped]: unrecognized selector sent to instance 0x7fb8fb6ae650
If I implement the method inside the ViewController, it works fine. But I'd be duplicating the code in all viewControllers which conform to the protocol.
Anything I'm doing wrong here?
Thanks in advance.
It seems like using protocol extensions are not supported at this point in time. According to fluidsonic's answer here:
In any case all functions you intend to use via selector should be marked with dynamic or #objc. If this results in an error that #objc cannot be used in this context, then what you are trying to do is simply not supported."
In your example, I think one way around this would be to create a subclass of UIBarButtonItem that calls a block whenever it is tapped. Then you could call containerDelegate?.toggleSideMenu() inside that block.
This compiles but crash also in Xcode7.3 Beta so finally you should use a ugly super class as target of the action, that i suppose that it's what you and me are trying to avoid.
This is an old question but I also ran into the same issue and came up with a solution which may not be perfect but it's the only way I could think of.
Apparently even in Swift 3, it's not possible to set a target-action to your protocol extension. But you can achieve the desired functionality without implementing your func menuTapped() method in all your ViewControllers that conforms to your protocol.
first let's add new methods to your protocol
protocol CenterViewControllerProtocol: class {
var containerDelegate: ContainerViewControllerProtocol? { get set }
//implemented in extension
func setupMenuBarButton()
func menuTapped()
//must implement in your VC
func menuTappedInVC()
}
Now change your extention like this
extension CenterViewControllerProtocol where Self: UIViewController {
func setupMenuBarButton() {
let barButton = UIBarButtonItem(title: "Menu", style: .Done, target: self, action: "menuTappedInVC")
navigationItem.leftBarButtonItem = barButton
}
func menuTapped() {
containerDelegate?.toggleSideMenu()
}
}
Notice now button's action is "menuTappedInVC" in your extension, not "menuTapped" . And every ViewController that conforms to CenterViewControllerProtocol must implement this method.
In your ViewController,
class MapViewController: UIViewController, CenterViewControllerProtocol {
weak var containerDelegate: ContainerViewControllerProtocol?
override func viewDidLoad() {
super.viewDidLoad()
setupMenuBarButton()
}
func menuTappedInVC()
{
self.menuTapped()
}
All you have to do is implement menuTappedInVC() method in your VC and that will be your target-action method. Within that you can delegate that task back tomenuTapped which is already implemented in your protocol extension.
I think you can wrap Target-Action to make Closure from them and then use it in similar way I have used Target-Action for UIGestureRecognizer
protocol SomeProtocol {
func addTouchDetection(for view: UIView)
}
extension SomeProtocol {
func addTouchDetection(for view: UIView) {
let tapGestureRecognizer = UITapGestureRecognizer(callback: { recognizer in
// recognizer.view
})
view.addGestureRecognizer(tapGestureRecognizer)
}
}
// MARK: - IMPORTAN EXTENSION TO ENABLE HANDLING GESTURE RECOGNIZER TARGET-ACTIONS AS CALLBACKS
extension UIGestureRecognizer {
public convenience init(callback: #escaping (_ recognizer: UIGestureRecognizer) -> ()) {
let wrapper = CallbackWrapper(callback)
self.init(target: wrapper, action: #selector(CallbackWrapper.callCallback(_:)))
// retaint callback wrapper
let key = UnsafeMutablePointer<Int8>.allocate(capacity: 1);
objc_setAssociatedObject(self, key, wrapper, .OBJC_ASSOCIATION_RETAIN)
}
class CallbackWrapper {
var callback : (_ recognizer: UIGestureRecognizer) -> ();
init(_ callback: #escaping (_ recognizer: UIGestureRecognizer) -> ()) {
self.callback = callback;
}
#objc public func callCallback(_ recognizer: UIGestureRecognizer) {
self.callback(recognizer);
}
}
}

Resources