I am following MVP architecture for developing an iOS app.The app is quite simple in which onViewDidLoad() I call a web service which returns me some data and I display that data in a table view.
ViewController:
class A : UIViewController{
var presenter : MyPresenter?
override func viewDidLoad() {
presenter = MyPresenter(delegate:self)
presenter.callWS()
}
}
extension A : Mydelegate{
func onSuccess(){
//this doesnt allow my viewcontroller to deint
tablview.delegate=self
tableview.datasource=self
tableview.reloadData()
}
}
protocol MyDelegate : class{
func onSuccess()
}
class MYPresenter {
weak var delegate : MyDelegate?
init(MyDelegate) {
self.delegate=delegate
}
func callWS(){
delegate.onSuccess()
}
}
This onSucces of MyDelegate does not allow my A viewcontroller to deint
Please let me know what i am doing wrong?
I've slightly modified version of your code and run it in a playground:
import UIKit
import PlaygroundSupport
class A : UITableViewController {
var presenter : MyPresenter?
override func viewDidLoad() {
presenter = MyPresenter(delegate:self)
presenter?.callWS()
let gesture = UITapGestureRecognizer(target: self, action: #selector(dismissOnTap))
view.addGestureRecognizer(gesture)
}
func dismissOnTap() {
dismiss(animated: true)
}
deinit {
print("Bye VC")
}
}
extension A : MyDelegate {
func onSuccess(){
//this doesnt allow my viewcontroller to deint
tableView.delegate = self
tableView.dataSource = self
tableView.reloadData()
}
}
protocol MyDelegate : class {
func onSuccess()
}
class MyPresenter {
weak var delegate : MyDelegate?
init(delegate: MyDelegate) {
self.delegate = delegate
}
func callWS() {
delegate?.onSuccess()
}
deinit {
print("Bye")
}
}
class B: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
let gesture = UITapGestureRecognizer(target: self, action: #selector(showOnTap))
view.addGestureRecognizer(gesture)
}
func showOnTap() {
let vc = A(style: .plain)
present(vc, animated: true)
}
}
let b = B()
b.view.frame = CGRect(x: 0, y: 0, width: 400, height: 600)
PlaygroundPage.current.liveView = b.view
And everything is deallocating properly. I guess the retain cycle is somewhere else and it's hard to find based on provided code.
Related
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)
}
}
So what I'm trying to do is pass a String and an Int back from one ViewController (NewCellViewController) to the previous one (SecondScreenViewController) when I close it. I added a print statement in the method in SecondScreenViewController that is supposed to receive this data, and it didn't print so I guess the method never ran. This is my code (removed some stuff to only include whats relevant):
SecondScreenViewController:
import UIKit
protocol DataDelegate {
func insertEvent(eventString: String, pos: Int)
}
class SecondScreenViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, DataDelegate {
var eventNames = ["event1", "event2", "event3"]
override func viewDidLoad() {
super.viewDidLoad()
advance()
}
//DataDelegate methods
func insertEvent(eventString: String, pos: Int)
{
print("if this prints, it worked")
if pos == -1
{
eventNames.append(eventString)
}
else
{
eventNames.insert(eventString, at: pos)
}
}
#objc func advance()
{
let vc = NewCellViewController(nibName: "NewCellViewController", bundle: nil)
vc.delegate = self
present(vc, animated: true, completion: nil)
}
}
NewCellViewController:
import UIKit
class NewCellViewController: UIViewController {
var delegate:DataDelegate?
#IBOutlet var addEventName: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addItem() {
insertNewEvent()
dismiss(animated: true, completion: nil)
}
func insertNewEvent()
{
let eventName = addEventName!.text
delegate?.insertEvent(eventString: eventName!, pos: -1) //add different positions
}
}
I have used the same controller name as yours, just to make you understand better.
SecondScreenViewController
import UIKit
class SecondScreenViewController: UIViewController {
var eventNames = ["event1", "event2", "event3"]
override func viewDidLoad() {
super.viewDidLoad()
//advance()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
advance()
}
private func advance() {
// Dont forget to add `Storyboard ID` as "NewCellViewController" on your Main.storyboard.
// See the image attached below.
if let vc = self.storyboard?.instantiateViewController(identifier: "NewCellViewController") as? NewCellViewController {
vc.delegate = self
present(vc, animated: true)
}
}
}
// Better this way.
extension SecondScreenViewController: DataDelegate {
func insertEvent(eventString: String, pos: Int) {
print(eventString, pos)
}
}
NewCellViewController
import UIKit
// Better to create protocols here.
protocol DataDelegate: class {
func insertEvent(eventString: String, pos: Int)
}
class NewCellViewController: UIViewController {
weak var delegate: DataDelegate?
#IBOutlet var addEventName: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addItem() {
insertNewEvent()
dismiss(animated: true)
}
private func insertNewEvent() {
delegate?.insertEvent(eventString: "your text", pos: -1)
}
}
Hope, this helps.
You can try using segue in the first VC to push the Second VC and pop the Second VC and come back to First VC. This might help you work with the delegate.
And also you can use the UserDefaults to pass and synchronize such values.
Depending on what exactly you want to pass you could use user defaults. Not recommended by many but I feel for simple data it's really quick and effective
When the user pushes the button in ViewController1, I want it to call a function in ViewController2. I think the only thing I'm missing is assigning ViewController2 as its own delegate, but how do you declare self as delegate?
ViewController1
protocol VC1Delegate {
func pushThisButton(_ sender: UIButton)
}
class ViewController1: UIViewController {
var delegate: VC1Delegate!
var theButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
buildButton()
}
func pushButton(_ sender: UIButton) {
delegate.pushThisButton(theButton)
}
func buildButton() {
theButton = UIButton(frame: CGRect(x: 100, y: 100, width: 200, height: 50))
theButton.backgroundColor = UIColor.black
theButton.addTarget(self, action: #selector(pushButton), for: .touchUpInside)
self.view.addSubview(theButton)
}
}
View Controller 2
class ViewController2: UIViewController, VC1Delegate {
// I'm guessing somewhere here I need to declare myself as the delegate?
override func viewDidLoad() {
super.viewDidLoad()
}
func pushThisButton(_ sender: UIButton) {
print("Damnit, Jack! Push the damn button!")
}
}
When you instantiate VC2, you should assign delegate to it.
For example:
protocol VC1Delegate: class {
func pushThisButton(_ sender: UIButton)
}
class ViewController1: UIViewController {
weak var delegate: VC1Delegate?
...
let vc2 = ViewController2()
self.delegate = vc2 // we are in the VC1 context
self.navigationController.pushViewController(vc2, animated: true)
I'm trying to call the function updateProgress from a LoadingDataHelper object but my delegate is not being called. I think the problem is that this LoadingDataHelper is not connected to the LoadingDataViewController (I mean like when you're having a UIView and a viewController).
LoadingDataHelper
protocol LoadingNewDataDelegate: class {
func updateProgress(progress : Float)
}
class LoadingDataHelper: NSObject {
var delegate: LoadingNewDataDelegate?
static let shared = LoadingDataHelper() // shared instance
func loginUser() {
//more code
updateProgressInViewController()
//more code
}
func updateProgressInViewController() {
delegate?.updateProgress(0.3)
}
}
LoadingDataViewController
class LoadingDataViewController: UIViewController, LoadingNewDataDelegate {
let loadingDataHelper: LoadingDataHelper = LoadingDataHelper()
override func viewDidLoad() {
super.viewDidLoad()
loadingDataHelper.delegate = self
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if (NSUserDefaults.standardUserDefaults().boolForKey("approvedTermsOfUse")) {
self.updateProgress(0.1)
LoadingDataHelper.shared.loginUser()
} else {
self.askForTerms()
}
}
func updateProgress(progress : Float) {
self.progressBar.setProgress(progress, animated: true)
self.progressBar.setNeedsDisplay()
}
}
Is there a way to solve this?
There is some wrong in above codeL:
In LoadingDataViewController, you are creating static property for LoadingDataHelper class and setting delegate to that class. But you are call "loginUser" using direct call.
You need to change like below:
class LoadingDataViewController: UIViewController, LoadingNewDataDelegate {
**//Change to shared initialisation of object//**
let loadingDataHelper: LoadingDataHelper = LoadingDataHelper.shared
override func viewDidLoad() {
super.viewDidLoad()
loadingDataHelper.delegate = self
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if (NSUserDefaults.standardUserDefaults().boolForKey("approvedTermsOfUse")) {
self.updateProgress(0.1)
**//Call using static object instead of new instance or direct call//**
loadingDataHelper.loginUser()
} else {
self.askForTerms()
}
}
func updateProgress(progress : Float) {
self.progressBar.setProgress(progress, animated: true)
self.progressBar.setNeedsDisplay()
}
}
This will make your controller to get delegate call back.
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
}
}