So I'm trying to separate out my delegate into a separate class instead of housing it in my cell class. Here's how I declare and initialize the delegate:
class RegistrationTableViewController: BaseTableViewController, UITextFieldDelegate {
var textFieldDelegate: UITextFieldDelegate
//MARK: - Initialization
required init?(coder aDecoder: NSCoder) {
textFieldDelegate = RegistrationTextFieldDelegate()
super.init(coder: aDecoder)
}
Then I set it in my general cell setup method:
func setupBaseCellWithType(type: Constants.Registration.CellType) -> BaseCell {
var cell: BaseCell? = nil
if let newCell = tableView.dequeueReusableCellWithIdentifier(Constants.Registration.profileBaseRegistrationCell) as? BaseProfileCell {
newCell.cellType = type
newCell.setupCell()
newCell.setTextFieldDelegate(textFieldDelegate)
cell = newCell
}
return cell!
}
The problem is, it never hits any of the delegate methods. It was working great when I made the cells my delegate, any idea whats going wrong here?
Edit: setTextFieldDelegate calls this method:
private func setTextFieldDelegates(delegate: UITextFieldDelegate) -> Void {
for textField in textFieldArray {
textField.delegate = delegate
}
}
That was how I set the delegate on all my textFields in the cell previously, so I know it's correctly setting them there, but something is going wrong in me trying to move it to another class.
Edit 2: I'm an idiot who's running on low sleep. I was only changing the BaseCell delegate, not my PhoneNumberCellDelegate... Feel free to call me an idiot if you'd like.
Related
This is probably the silliest question to date - but I am having problems with initializing a subclassed view controller with a customized required coder initializer (specifically using the QRCoder pod; and to not expose code I don't own, I'll be using example classes in my case).
Here are the essentials.
class A: UIViewController {
public var name = String()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
name = "This is a test"
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
Then we have...
class B: A {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
name = "What what"
}
}
If I attempt to generate a new view controller of B in say, a button tap on C...
class C: UIViewController {
let button = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
button(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)}
}
#objc func buttonTapped(_ sender: Any) {
let viewOfB = B()
present(viewOfB, animated: true)
}
}
It doesn't compile = because my call of let viewOfB = B() is missing the coder parameter.
The problem is, if I add a coder parameter, what in the world do I put in there? I've tried filling it with just an empty(?) NSCoder, like so
let emptyCoder = NSCoder()
let viewOfB = B(coder: emptyCoder)
But then upon a run and button tap, I get the following error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -decodeObjectForKey: cannot be sent to an abstract object of class NSCoder: Create a concrete instance!'
I've tried adding a convenience initializer to A (where I have to run a self.init instead of super.init for some reason), but doing that gives me an EXC_BAD_ACCESS_ERROR.
What exactly do I need to provide to an init:(coder) instance to be able to... get things going?
What exactly do I need to provide to an init:(coder) instance to be able to... get things going?
Nothing. Stop thinking about init(coder:).
Here's the actual problem. When you say B(), you are calling init(). But there is no init(), because where would it come from? You didn't implement any such method in A. And it is not inherited from the superclass (UIViewController), because you effectively destroyed all inherited initializers when you implemented init(coder:).
So if you want to say B(), you must implement init() explicitly in A, yourself, like this:
class A: UIViewController {
public var name = String()
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
name = "This is a test"
}
}
Swift 4.0 iOS 11.x
I have created a simple text field class, that uses the UITextFieldDelegate. I wanted to add to it an additional protocol that I could use to pass on the fact that the text entry to said field completed. A Delegate chain, since once I have picked up the fact that text entry has exited in the custom class I cannot pass it down to the VC in which the UITextField class is within it seems.
import UIKit
protocol ExitedFieldDelegate {
func exited(info: String)
}
class IDText: UITextField, UITextFieldDelegate {
internal var zeus: ExitedFieldDelegate? = nil
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
delegate = self
}
required override init(frame: CGRect) {
super.init(frame: frame)
delegate = self
}
func textFieldDidBeginEditing(_ textField: UITextField) {
self.textColor = UIColor.black
}
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextFieldDidEndEditingReason) {
if (delegate != nil) {
let info = self.text
zeus?.exited(info: info!)
}
}
}
I added this code to the viewController I wanted to use my custom class within.
class ConfigViewController: UIViewController, ExitedFieldDelegate
And of course the method required by the protocol
func exited(info: String) {
print("The brain has left the room")
}
And I made it a delegate of said protocol so I got this in effect
var blah = IDText()
blah.delegate = self
But well it doesn't work. Am I attempting the impossible here, should I simply use default notifications instead? or indeed something else?
By setting:
blah.delegate = self
You are overwriting setting the delegate to self in the initializers.
What you want is to rewrite:
internal var zeus: ExitedFieldDelegate? = nil
to:
weak var zeus: ExitedFieldDelegate?
To be able to use weak (you want that to prevent retain cycle), update protocol definition to:
protocol ExitedFieldDelegate: class {
func exited(info: String)
}
And then change this:
var blah = IDText()
blah.delegate = self
to:
var blah = IDText()
// you want to set zeus instead of the delegate field
blah.zeus = self
I created a nib file with a custom collectionViewCell and attached to a viewController
class CustomCollectionView: UICollectionViewCell{}
Now I have to use the exact cell inside a tableView. I created a new nib file and viewController
class CustomTableView: UITableViewCell{}
and I copied the hole code of CustomCollectionView on it. every thing is working fine but I believe that it dose not make sense to copy the hole exact code of CustomCollectionView into CustomTableView and to use the exact same nib file but with a tableViewCell instead of collectionViewCell on it. Is there any way to optimize what I did?
As you said in a comment in suhit's answer, you can do this by using a common view in both the CollectionViewCell and TableViewCell subclasses. You don't need a ViewController since it adds extra overhead. A simple UIView is enough. Some code to show what I mean:
class CustomTableViewCell: UITableViewCell {
var customView: CustomView!
func awakeFromNib() {
super.awakeFromNib()
customView = CustomView()
customView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(customView)
customView.fillSuperview()
}
}
class CustomCollectionViewCell: UICollectionViewCell {
var customView: CustomView!
func awakeFromNib() {
super.awakeFromNib()
customView = CustomView()
customView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(customView)
customView.fillSuperview()
}
}
extension UIView {
func fillSuperview() {
guard let superview = superview else {
return print("no superview")
}
topAnchor.constraint(equalTo: superview.topAnchor).isActive = true
bottomAnchor.constraint(equalTo: superview.bottomAnchor).isActive = true
leftAnchor.constraint(equalTo: superview.leftAnchor).isActive = true
rightAnchor.constraint(equalTo: contentVisuperviewew.rightAnchor).isActive = true
}
}
A sample implementation for the CustomView class:
class CustomView: UIView {
func initialize() {
//...
}
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
}
If you wish to create your custom view in a xib that's also fine, but it's a little trickier. This is beyond the scope of the question but I'm just going to leave a link here in case you need it.
If you want to use same view then its better to use similar type view i.e. use collectionView at both places so that you can use the CustomCollectionViewCell in both ViewControllers. UICollectionView is highly customisable so you can do whatever you want to do with UITableView in UICollectionView as well.
How to create a Custom delegate with a delegate method for a custom View with its(custom View's) object as arguement in that delegate method just like tableView's cellForRowAtIndexPath: method?
Elaboration of the question with the exact problem:
Till now I have always used the delegate methods of different views provided in UIKit to construct views in my application. But this time I need to make a custom View but with the custom delegate and data source. I already made it. But a question always lifts. That is, whenever I need multiple same custom Views in my ViewController, then how will I recognise them seperately in my delegate methods. Definitely I need to pass the custom View's object in my delegate method as arguement. For eg.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath;
In above delegate method, tableView object is an argument using which we can recognise the tableView if there are multiple tableViews on one ViewController and this View Controller is set as the delegate of all these tableViews.
This is the real scenario:
//
// ViewController.swift
// DemoCustomViewDelegate
//
// Created by Shubham Ojha on 4/3/16.
// Copyright © 2016 Shubham Ojha. All rights reserved.
//
import UIKit
protocol MyCustomViewDelegate {
func myCustomView(myCustomView: MyCustomView, didSomethingWithAString aString: String)
}
class MyCustomView: UIView {
var delegate: AnyObject?
init(frame: CGRect, andViewId viewId: String, andDelegate delegate: AnyObject) {
super.init(frame: frame)
self.delegate = delegate
self.doSomethingWithAString(viewId)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
func doSomethingWithAString(aString: String) {
// do something with a string
delegate!.myCustomView(self, didSomethingWithAString: aString)
}
}
class ViewController: UIViewController {
var myView: MyCustomView!
override func viewDidLoad() {
super.viewDidLoad()
myView = MyCustomView.init(frame: CGRectMake(0, 0, 50, 50), andViewId: "100", andDelegate: self)
//set the delegate
myView.delegate = self
}
func myCustomView(myCustomView: MyCustomView, didSomethingWithAString aString: String) {
//this delegate method is called but the object of myCustomView I need could not get
if(myCustomView.isEqual(myView)){
print("Got the object")//**Does not prints, this is what I actually need to print...**
}
}
}
You can copy-paste the code in your ViewController and evaluate the problem.
Answers in swift are also welcome!
is this what you mean?
protocol MyCustomViewDelegate {
func myCustomView(myCustomView: MyCustomView, didSomethingWithAString aString: String)
}
class MyCustomView: UIView {
var delegate: MyCustomViewDelegate?
func doSomethingWithAString(aString: String) {
// do something with a string
// inform the delegate
delegate?.myCustomView(self, didSomethingWithAString: aString)
}
}
The code you posted is correct.
When in your ViewController you do something like
let myView = MyCustomView()
myView.delegate = self
You're telling to the MyCustomView object to call self as delegate.
So, when MyCustomView executes this code
delegate!.myCustomView(self, didSomethingWithAString: aString)
It calls this in your code
func myCustomView(myCustomView: MyCustomView, didSomethingWithAString aString: String) {
//this delegate method is called but the object of myCustomView I need could not get
if(myCustomView.isEqual(myView)){
print("Got the object")//**Does not prints, this is what I actually need to print...**
}
}
where myCustomView is equal to myView. May you should compare them by using the == operator instead of isEqual.
You can look up extensions like:
extension ViewController: UITableViewDelegate {
// You extension to the UITableViewDelegate class
}
protocol MyCustomViewDelegate {
func myCustomView(myCustomView: MyCustomView, didSomethingWithAString aString: String)
}
class MyCustomView: UIView {
var delegate: MyCustomViewDelegate?
func doSomethingWithAString(aString: String) {
// do something with a string
if let delegate = delegate {
delegate.myCustomView(self, didSomethingWithAString: aString)
}
}
}
class ViewController: UIVIewController {
var myView: MyCustomView!
override func viewDidLoad() {
super.viewDidLoad()
myView = MyCustomView()
//set the delegate
myView.delegate = self
}
}
//implement the delegate
extension ViewController: MyCustomViewDelegate {
func myCustomView(myCustomView: MyCustomView, didSomethingWithAString aString: String) {
//get the string from delegation
}
}
I am trying to setup a master details navigation.
I use storyboard, master is a dynamic table and details is a static table.
I have a nameLabel setup as an outlet in the controller but when i try to access it in viewDidLoad, its still set to nil.
Instead of using prepareForSegue, I have used didSelectRowAtIndexPath which pushes the detail view like this: (because i'm using the TableViewBindingHelper, see https://github.com/ColinEberhardt/ReactiveTwitterSearch/tree/master/ReactiveTwitterSearch/Util)
func showLessonView(lessonVM: LessonViewModel) {
let lessonViewController = LessonViewController(WithViewModel: lessonVM)
self.navigationController?.pushViewController(lessonViewController, animated: true)
}
LessonViewController:
import Foundation
import ReactiveCocoa
class LessonViewController: UITableViewController {
#IBOutlet var lessonNameLabel: UILabel!
private var viewModel: LessonViewModel
init(WithViewModel viewModel: LessonViewModel){
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
bindData()
}
func bindData() {
// null here!
if (lessonNameLabel != nil) {
lessonNameLabel.rac_text <~ viewModel.name
}
}
}
How can I fix this?
Other sample code i have seen performs the navigation in segue which ends up calling the init(coder aDecoder: NSCoder) constructor and all the outlets are already initialized.
Because you initialise the view controller with the WithViewModel initialiser, it knows nothing about the storyboard and so the outlets are not hooked up. To get the outlets hooked up as specified in the storyboard, you need either to use a segue, or to use the storyboard's instantiateViewControllerWithIdentifier(identifier:) method to create the view controller. Either way, you can't (easily) pass the ViewModel as an argument for the initialisation, so you will need to expose the viewModel var (remove private) and set it separately in your showLessonView method. To use instantiateViewControllerWithIdentifier(identifier:), give your Lesson View Controller an identifier (say "LessonViewController") in the storyboard. Then amend your showLessonView as follows:
func showLessonView(lessonVM: LessonViewModel) {
let lessonViewController = self.storyboard!.instantiateViewControllerWithIdentifier(identifier:"LessonViewController") as! LessonViewController
lessonViewController.viewModel = lessonVM
self.navigationController?.pushViewController(lessonViewController, animated: true)
}
When a view controller is instantiated from a storyboard, the init(coder:) initialiser is used, so either remove the override of that method, or amend it to call the super implementation:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}