Function not getting called with protocol delegate and view controller swift - ios

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)
}
}

Related

i want to triger navigationcontroller when i press button in UIView class

I want to trigger Navigation controller to some other screen when i press the button in UIView class. How can i do this?
//Code for UIView Class in Which Button Iboutlet is created
import UIKit
protocol ButtonDelegate: class {
func buttonTapped()
}
class SlidesVC: UIView {
var delegate: ButtonDelegate?
#IBAction func onClickFinish(_ sender: UIButton) {
delegate?.buttonTapped()
}
#IBOutlet weak var imgProfile: UIImageView!
}
//ViewController Class code in Which Button Protocol will be entertained
class SwipingMenuVC: BaseVC, UIScrollViewDelegate {
var slidesVC = SlidesVC()
override func viewDidLoad() {
super.viewDidLoad()
slidesVC = SlidesVC()
// add as subview, setup constraints etc
slidesVC.delegate = self
}
extension BaseVC: ButtonDelegate {
func buttonTapped() {
self.navigationController?.pushViewController(SettingsVC.settingsVC(),
animated: true)
}
}
A more easy way is to use typealias. You have to write code in 2 places. 1. your viewClass and 2. in your View Controller.
in your SlidesView class add a typealias and define param type if you need otherwise leave it empty.
class SlidesView: UIView {
typealias OnTapInviteContact = () -> Void
var onTapinviteContact: OnTapInviteContact?
#IBAction func buttonWasTapped(_ sender: UIButton) {
if self.onTapinviteContact != nil {
self.onTapinviteContact()
}
}
}
class SwipingMenuVC: BaseVC, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let slidesView = SlidesView()
slidesView.onTapinviteContact = { () in
// do whatever you want to do on button tap
}
}
You can use the delegate pattern to tell the containing ViewController that the button was pressed and let it handle whatever is needed to do next, The view doesn't really need to know what happens.
A basic example:
protocol ButtonDelegate: class {
func buttonTapped()
}
class SomeView: UIView {
var delegate: ButtonDelegate?
#IBAction func buttonWasTapped(_ sender: UIButton) {
delegate?.buttonTapped()
}
}
class ViewController: UIViewController {
var someView: SomeView
override func viewDidLoad() {
someView = SomeView()
// add as subview, setup constraints etc
someView.delegate = self
}
}
extension ViewController: ButtonDelegate {
func buttonTapped() {
self.showSomeOtherViewController()
// or
let vc = NewViewController()
present(vc, animated: true)
}
}

iOS (Swift): Protocol for UIViewController that adds a new object

I have a view controller that is responsible for adding a new object, say a new contact. This view controller (AddContactViewController) has the following UIBarButtonItem on a UINavigationBar, which is starts of disabled until enough information is provided to enable it. Then when this button is pressed a method (doneButtonPressed) is called.
The layout is as follows:
class AddContactViewController: UIViewController {
#IBOutlet weak var doneButton: UIBarButtonItem! {
didSet {
doneButton.isEnabled = false
doneButton.target = self
doneButton.action = #selector(self.doneButtonPressed)
}
}
#objc fileprivate func doneButtonPressed() {
// do some stuff ...
self.dismiss(animated: false, completion: nil)
}
}
As this is quite a common thing to have and there's a lot of boiler plate code, I've been working on a protocol AddingHandler but haven't quite worked out how to have UIBarButtonItem as a weak variable which hooks up to a storboard or if this is even the right way to go.
protocol AddingHandler {
var doneButton: UIBarButtonItem? { get set }
func doneButtonPressed()
}
extension protocol where Self: UIViewController {
func configureDoneButton() {
doneButton.isEnabled = false
doneButton.target = self
doneButton.action = #selector(self.doneButtonPressed)
}
}
Any help or comments in making this work would be much appreciated.
The problem How is best to add a weak UIButton to a protocol which can then be hooked up in a story board where UIViewController implements it? As there is a lot of repetitive code here should I wish to have another AddSomethingViewController I was wondering if there was a neater way of only writing this once (in a protocol with an extension) then calling the protocol in any view controller that is adding something new ...
You can simply configure the doneButton in viewDidLoad()
override func viewDidLoad()
{
super.viewDidLoad()
doneButton.isEnabled = false
doneButton.target = self
doneButton.action = #selector(self.doneButtonPressed)
}
Edit 1:
#objc protocol AddingHandler
{
var doneButton: UIBarButtonItem? { get }
#objc func doneButtonPressed()
}
extension AddingHandler where Self: UIViewController
{
func configureDoneButton()
{
doneButton?.isEnabled = false
doneButton?.target = self
doneButton?.action = #selector(doneButtonPressed)
}
}
class AddContactViewController: UIViewController, AddingHandler
{
#IBOutlet weak var doneButton: UIBarButtonItem!
override func viewDidLoad()
{
super.viewDidLoad()
configureDoneButton()
}
func doneButtonPressed()
{
// do some stuff ...
self.dismiss(animated: false, completion: nil)
}
}
I've used ObjC runtime to resolve the issue. Try implementing it at your end and check if it works for you.

Calling selector from protocol extension

I'm building simple theme engine and would like have an extension which adds UISwipeGestureRecognizer to UIViewController
Here is my code:
protocol Themeable {
func themeDidUpdate(currentTheme: Theme) -> Void
}
extension Themeable where Self: UIViewController {
func switchCurrentTheme() {
Theme.switchTheme()
themeDidUpdate(Theme.currentTheme)
}
func addSwitchThemeGestureRecognizer() {
let gestureRecognizer = UISwipeGestureRecognizer(target: self, action:#selector(Self.switchCurrentTheme))
gestureRecognizer.direction = .Down
gestureRecognizer.numberOfTouchesRequired = 2
self.view.addGestureRecognizer(gestureRecognizer)
}
}
Of course compiler can't find #selector(Self.switchCurrentTheme) as it isn't exposed via #objc directive. Is it possible to add this behaviour to my extension?
UPDATE: Theme is a Swift enum, so I can't add #objc in front of Themeable protocol
The cleanest, working solution I could come up with was to define a private extension on UIViewController with the method in question. By limiting the scope to private, access to this method is isolated to within the source file where the protocol is defined in. Here's what it looks like:
protocol Themeable {
func themeDidUpdate(currentTheme: Theme) -> Void
}
fileprivate extension UIViewController {
#objc func switchCurrentTheme() {
guard let themeableSelf = self as? Themeable else {
return
}
Theme.switchTheme()
themeableSelf.themeDidUpdate(Theme.currentTheme)
}
}
extension Themeable where Self: UIViewController {
func addSwitchThemeGestureRecognizer() {
let gestureRecognizer = UISwipeGestureRecognizer(target: self, action:#selector(switchCurrentTheme))
gestureRecognizer.direction = .Down
gestureRecognizer.numberOfTouchesRequired = 2
self.view.addGestureRecognizer(gestureRecognizer)
}
}
I found a solution. May be not the perfect one, but it works.
As I can't define Themeable protocol as #objc because it uses Swift-only enum I decided to move method I want to call to "parent" protocol and define this protocol as #objc. It seems like it works but I don't really like it to be honest...
#objc protocol ThemeSwitcher {
func switchCurrentTheme()
}
protocol Themeable: ThemeSwitcher {
func themeDidUpdate(currentTheme: Theme) -> Void
}
extension Themeable where Self: UIViewController {
func switchCurrentTheme() {
Theme.switchTheme()
themeDidUpdate(Theme.currentTheme)
}
func addSwitchThemeGestureRecognizer() {
let gestureRecognizer = UISwipeGestureRecognizer(target: self, action:#selector(switchCurrentTheme))
gestureRecognizer.direction = .Down
gestureRecognizer.numberOfTouchesRequired = 2
self.view.addGestureRecognizer(gestureRecognizer)
}
}
Have you considered creating a wrapper to let you call your non-#objc function from an #objc one?
#objc class Wrapper: NSObject {
let themeable: Themeable
init(themeable: Themeable) {
self.themeable = themeable
}
func switchCurrentTheme() {
Theme.switchTheme()
themeable.themeDidUpdate(Theme.currentTheme)
}
}
protocol Themeable {
func themeDidUpdate(currentTheme: Theme) -> Void
}
extension Themeable where Self: UIViewController {
func addSwitchThemeGestureRecognizer() {
let wrapper = Wrapper(themeable: self)
let gestureRecognizer = UISwipeGestureRecognizer(target: wrapper, action:#selector(Wrapper.switchCurrentTheme))
gestureRecognizer.direction = .Down
gestureRecognizer.numberOfTouchesRequired = 2
self.view.addGestureRecognizer(gestureRecognizer)
}
}
Here is a similar use-case, you can call a method through a selector without using #objc as in swift by using the dynamic keyword. By doing so, you are instructing the compiler to use dynamic dispatch implicitly.
import UIKit
protocol Refreshable: class {
dynamic func refreshTableData()
var tableView: UITableView! {get set}
}
extension Refreshable where Self: UIViewController {
func addRefreshControl() {
tableView.insertSubview(refreshControl, at: 0)
}
var refreshControl: UIRefreshControl {
get {
let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
if let control = _refreshControl[tmpAddress] as? UIRefreshControl {
return control
} else {
let control = UIRefreshControl()
control.addTarget(self, action: Selector(("refreshTableData")), for: .valueChanged)
_refreshControl[tmpAddress] = control
return control
}
}
}
}
fileprivate var _refreshControl = [String: AnyObject]()
class ViewController: UIViewController: Refreshable {
#IBOutlet weak var tableView: UITableView! {
didSet {
addRefreshControl()
}
}
func refreshTableData() {
// Perform some stuff
}
}

Swift Delegate not being called to close a UIViewController

I have a CenterViewController which contains a Game Controller. I want to add/remove a RulesViewController that the user can easily refer to as they play.
The RulesViewController appears and is dismissed fine. But the delegate.continueGame method is never called. I've added the protocol to RulesViewController. I've added a class extension to CenterViewController to handle the delegate. What am I missing?? Any help much appreciated...
Class CenterViewController: UIViewController {
private var controller: GameController
required init(coder aDecoder: NSCoder){
controller = GameController()
}
override func viewDidLoad() {
// add all the views here
let gameView = UIView(frame: CGRectMake(0,0, ScreenWidth, ScreenHeight))
self.view.addSubview(gameView)
controller.gameView = gameView
}
// method called when rules button on the gameView is pressed
func showRulesForLevel () {
let rulesViewController = storyboard!.instantiateViewControllerWithIdentifier("RulesViewController") as! RulesViewController
presentViewController(rulesViewController, animated: true, completion: nil)
// extension to the Class to handle the delegate
extension CenterViewController: RulesViewControllerDelegate {
//func to continue the game
func continueGame() {
controller.gameView.userInteractionEnabled = true
}
}
In the RulesViewController I have:
protocol RulesViewControllerDelegate {
func continueGame()
}
class RulesViewController: UIViewController {
var delegate: RulesViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// code to add a continue button which when pressed calls continueGameAction method
}
func continueGameAction() {
// dismiss the UIViewController so game can continue
self.dismissViewControllerAnimated(true, completion: nil)
// continue the game in CenterViewController
delegate?.continueGame()
}
}
BUT delegate?.continueGame() is never called.
Ok so you need to set the delegate in showRulesForLevel method like this:
rulesViewController.delegate = self
:)

Swift: How to use property observers with delegation

I'm trying to use delegation and property observers together to know when a property changes. I setup the protocol but I'm not sure how to use property observers.
I have a class called GridView that is being added to DetailViewController. GridView has an array of ints called rowValues. I would like to observe rowValues from DetailViewController.
GridView.swift
protocol gridViewDelegate {
func rowValueChanged(value: [Int])
}
class GridView: UIView {
var rowValues = [0,0,0,0,0]
var delegate: gridViewDelegate?
func updateRowValue() {
rowValues[0] = 1
}
}
DetailViewController.swift
class DetailViewController: UIViewController, gridViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
var grid = GridView(frame: view.frame)
grid.delegate = self
view.addSubview(grid)
}
func rowValueChanged(value: [Int]) {
println(value)
}
}
Probably this is the syntax you are looking for:
class GridView: UIView {
var rowValues: [Int] = [0,0,0,0,0] {
didSet {
if let theDelegate = self.delegate {
theDelegate.rowValueChanged(rowValues)
}
}
}
var delegate: gridViewDelegate?
}

Resources