Swift TableViewDataSource Separate Other Class - swift

I have two examples
First
override func viewDidLoad() {
super.viewDidLoad();
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.dataSource = TableViewDataSource();
}
Second
var dataSource:TableViewDataSource!;
override func viewDidLoad() {
super.viewDidLoad();
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
dataSource = TableViewDataSource();
tableView.dataSource = dataSource;
}
First example does not work but second works. Are these not the same? And What is the difference between two examples?

first one not works because you have to set a strong reference to it
tableView.dataSource = TableViewDataSource();
while the other have it here
var dataSource:TableViewDataSource!;
//
If you looked to dataSource delegate implementation file , it's declared like this
weak open var dataSource:UITableViewDataSource?
look carfeully to weak so it doesn't retain vars assigned to it that's why the other class instance must be strongly referenced

UITableView dataSource is a weak property. Your first example doesn't work because there is no strong reference to the TableViewDataSource instance that you create. By the time the end of viewDidLoad is reached, the instance is deallocated and dataSource is reset back to nil.
Your second example works because your dataSource property of your class maintains a strong reference to the TableViewDataSource instance. As long as your view controller exists, the TableViewDataSource will exist and the table view's data source will work.

Related

Should I make delegate of custom cell as a weakly referenced proprety?

ViewController Code
class ViewController: UIViewController {
deinit {
print("ViewController deinitialised")
}
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
self.tableView.dataSource = self
}
func didTapBlue() {
}
}
extension ViewController: UITableViewDataSource, CustomCellDelegate {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myCell") as! CustomCell
cell.delegate = self
cell.textLabel!.text = "\(indexPath.row)"
return cell
}
func buttonTapped() {
print("Button tapped")
}
}
CustomCell Code
class CustomCell: UITableViewCell {
deinit {
print("Cell deinitialised")
}
var delegate: CustomCellDelegate! //When protocol Type is A
// weak prefix when protocol Type is B
// weak var delegate: CustomCellDelegate!
#IBAction func buttonClickAction(sender: AnyObject) {
if let del = self.delegate {
del.buttonTapped()
}
}
}
Protocol Type A
protocol CustomCellDelegate{
func buttonTapped()
}
Protocol Type B
protocol CustomCellDelegate: class {
func buttonTapped()
}
I am confused about what is the appropriate way to implement delegate pattern for passing message between Cell And ViewController. I know that if two objects hold each other's reference strongly, there will be a retain cycle and they won't get deallocated in the application lifetime.
In the above code, ViewController doesn't seem to hold reference of Cell. Hence I think it doesn't matter if I use protocol of type A and keep the strong reference of ViewController in cell.
But will my code be any safer if I declare the delegate property as a weakly referenced property? What are the implications of it?
Update:
Turns out that even if the ViewController is not holding direct reference of cell & even if TableView's reference is weak, ViewController is somehow holding strong reference to the cells. When I follow Method A, that is without declaring the delegate to be of weak reference. The deinit methods in Cell and ViewController never gets called. I checked in instruments too. The persistent retain count keeps increasing if I don't declare delegate as weak.
Now the big question is how is ViewController holding strong reference to the cells?
There are a couple things going on there.
Making every ViewController conform to UITableViewDelegate and UITableViewDatasource is needless since you already have UITableViewController and you'll probably need to override those methods anyway. You would be duplicating code at some point in your development lifecycle.
delegates always need to be a weak reference to avoid retain cycles.
Deinitialization Process:
When the view controller is popped out.
Then deinit method is called.
Then only all the other references that view controller is holding is cleared.
Parents deinit triggers, child's deinit triggers then after all the deinit is traversed through then deallocation of parent is done finally at last.
If any of the child is strongly referencing the parent. The deinit of parent never gets called and all the deinitialization process halts. In our case, since cell is retaining view controller strongly. The deinit method of ViewController never gets called. Hence The retain cycle.
Here is great explanation for retain cycle.

delegate is always nil

I have a custom UIView class called MyView and a View Controller.
When the user taps a button on the UIView, I want to call a function on the view controller. I'm trying to achieve this through delegation
custom UIClass
#objc protocol MyViewDelegate{
optional func expandCollapse()
}
class MyView: UIView, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate{
weak var delegate:MyViewDelegate?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if self.subviews.count == 0 {
loadNib()
}
}
override init(frame:CGRect){
super.init(frame: frame)
loadNib()
}
func loadNib(){
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "MyView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! MyView
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(view);
}
#IBAction func expandit(sender: AnyObject) {
//this is where it fails. delegate is nil
delegate!.expandCollapse!()
}
}
My View Controller
class ViewController2: UIViewController, MyViewDelegate {
#IBOutlet weak var theview: UIView!
var myview : MyView?
override func viewDidAppear(animated: Bool) {
super.viewWillAppear(animated)
myview = MyView(frame: CGRectMake(0,0,theview.frame.size.width,theview.frame.size.height))
self.theview.addSubview(myview!)
myview!.delegate = self
}
func expandCollapse() {
viewheight.constant = 172
UIView.animateWithDuration(0.5) {
self.view.layoutIfNeeded()
}
}
In the UIView, the delegate is always nil. What am I missing?
Using delegation for this is simply unsuitable. You are fighting UIKit design patterns.
The whole situation is very simple.
You have your ViewController.
Then you have your totally independent custom view.
Essentially, you want somehow to route the TouchUpInside event from the button to get to viewController.
If your Custom view contains a button, then the accessibility level of this button is internal by default. Looking at the code, I assume you created the button in Interface builder. Make an outlet from the custom view class to the button, so that there is a programatically accessible reference to it.
Your view controller declares an instance of this custom view. Then, in viewDidLoad you have to use the target-action pattern.
self.customView.button.addTarget(target: self, action: "expandCollapse", forControlEvents: .TouchUpInside)
That's basically all there is to it.
I'm not entirely confident of my ARC understanding, but I believe the issue is that your delegate is a weak reference and there's nothing keeping a reference to the delegate after it's set, so it' deallocated.
Replace it with this and I believe it will work:
var delegate:MyViewDelegate?
Try assigning the delegate to "myview" before adding it to "theview"
The problem is in the loadNib() member function.
You're creating two instances of "MyView". The second instance being added as a subview.
You're setting the delegate in one instance and referring to a nil delegate in the other instance.
Try using a static class method like below to create one instance of "MyView"
class func loadFromNib() -> MyView? {
guard let myView = Bundle.main.loadNibNamed("MyView", owner: nil, options: nil)?.first as? MyView else {
assertionFailure("Failed to load nib for 'MyView'!")
return nil
}
return myView
}
Doing it this way, you won't need the custom init()s either.
Hope that helps!

Pass data by protocol while using Container to view another ViewController in Swift

I started working on this question app.
I began by tableView of the categories:
For data exchange, I decided to use a protocol:
protocol Category {
func data(object:AnyObject)
}
In the first ViewController has the following code:
class ViewController: UIViewController {
var items:[String] = ["Desktop","Tablet","Phone"]
let CategoriesData:Category? = nil
override func viewDidLoad() {
super.viewDidLoad()
CategoriesData?.data(items)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
In the second ViewController (tableView in Container) have the following code:
class CategoriesViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, Category {
#IBOutlet var table: UITableView!
var items:[String] = []
func data(object: AnyObject) {
self.items = (object as? [String])!
print(object)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:TableViewCell = self.table.dequeueReusableCellWithIdentifier("SegueStage") as! TableViewCell
cell.nameLabel.text = items[indexPath.row]
return cell
}
}
For me, apparently it's all right. But nothing appeared on the simulator.
My question is: If the Container use to present another viewController as passing data by protocols should be done?
EDITED
I answered why the TO:s solution didn't work as intended, but I just realised that I haven't given a viable answer to how to use protocols as delegates for the ViewController -> ViewController communication. I'll leave the half-answer below until someone can possibly answer the full question better.
In the way protocol is used in your code, you define your protocol Category to be a delegate for instances of the type ViewController. When an instance of type ViewController is initialised in---and hence owned locally in the scope of---some other class, the instance can delegate callbacks to the owning class.
The problem is that your CategoriesViewController does not contain any instances of type ViewController. We note that both these classes are, in themselves, subclasses of UIViewController, but none of them contain instances of one another. Hence, your CategoriesViewController does indeed conform to protocol Category, by implemented the protocol method data(...), but there's no ViewController instance in CategoriesViewController that can do callbacks to this function. Hence, your code compile file, but as it is, method data(...) in CategoriesViewController will never be called.
I might be mistaken, but as far as I know, protocol delegates are used to do callbacks between models (for model in MVC design) and controllers (see example below), whereas in your case, you want a delegate directly between two controllers.
As an example of model-delegate-controller design, consider some custom user control, with some key property value (e.g. position in rating control), implemented as a subclass of UIView:
// CustomUserControl.swift
protocol CustomUserControlDelegate {
func didChangeValue(value: Int)
}
class CustomUserControl: UIView {
// Properties
// ...
private var value = 0 {
didSet {
// Possibly do something ...
// Call delegate.
delegate?.didChangeValue(value)
}
}
var delegate: CustomUserControlDelegate?
// ... some methods/actions associated with your user control.
}
Now lets assume an instance of your CustomUserControl is used in a a view controller, say ViewController. Your delegate functions for the custom control can be used in the view controller to observe key changes in the model for CustomUserControl, much like you'd use the inherent delegate functions of the UITextFieldDelegate for UITextField instances (e.g. textFieldDidEndEditing(...)).
For this simple example, use a delegate callback from the didSet of the class property value to tell a view controller that one of it's outlets have had associated model update:
// ViewController.swift
Import UIKit
// ...
class ViewController: UIViewController, CustomUserControlDelegate {
// Properties
// ...
#IBOutlet weak var customUserControl: CustomUserControl!
// Instance of CustomUserControl in this UIViewController
override func viewDidLoad() {
super.viewDidLoad()
// ...
// Custom user control, handle through delegate callbacks.
customUserControl.delegate = self
}
// ...
// CustomUserControlDelegate
func didChangeValue(value: Int) {
// do some stuff with 'value' ...
}
}

delegate passing nil in swift

I am trying to get to grips with delegates but the delegate I have set up seems to be nil and I am not sure why. I have a HomeViewController where the game is started from, then a UITableViewController where the player selects a row from a table. The row index is then used to pull data to be used in the game. The UITableViewController segues back to the HomeViewController where the game then starts. I thought I had put the correct protocol and delegate code in place but the delegate seems to be nil.
Any help much appreciated!
import UIKit
import Foundation
class HomeViewController: UIViewController, WordListsTableViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// sets up the game here
}
func wordListSelected(selectedWordList: Int) {
// passes the index path of the table to the AppWordList class to create the wordList for the game.
controller.wordList = AppWordList(wordListNumber: selectedWordList)
}
and in the TableViewController
import UIKit
protocol WordListsTableViewControllerDelegate {
func wordListSelected(selectedWordList: Int)
}
class WordListsTableViewController: UITableViewController {
var delegate: WordListsTableViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
reloadData()
tableView.reloadData()
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var selectedWordList = Int()
if (indexPath.section) == 2 {
selectedWordList = (indexPath.row) // Console shows the row is being selected ok.
delegate?.wordListSelected(selectedWordList) // IS NIL ???
// exit segue back to the HomeVC
performSegueWithIdentifier("startGameSegue", sender: nil)
}
}
You need to inform the HomeViewController class that has to be the delegate receiver for the class WordListsTableViewController, like this:
import UIKit
import Foundation
class HomeViewController: UIViewController, WordListsTableViewControllerDelegate
{
var wordListTableViewController = WordListTableViewController() // You forgot this
override func viewDidLoad() {
super.viewDidLoad()
wordListTableViewController.delegate = self // And this
// sets up the game here
}
func wordListSelected(selectedWordList: Int) {
// passes the index path of the table to the AppWordList class to create the wordList for the game.
controller.wordList = AppWordList(wordListNumber: selectedWordList)
}
You're missing a very important point about the Delegate Pattern, you need to keep a reference to the class that delegate its function and set it delegate in the class that handle the function. So let suppose you present the WordListsTableViewController by a segue from the HomeViewController like in the following example:
class HomeViewController: UIViewController, WordListsTableViewControllerDelegate {
// the reference to the class that delegate
var wordListTableViewController: WordListsTableViewController!
override func viewDidLoad() {
super.viewDidLoad()
// sets up the game here
}
func wordListSelected(selectedWordList: Int) {
// passes the index path of the table to the AppWordList class to create the wordList for the game.
controller.wordList = AppWordList(wordListNumber: selectedWordList)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// get the reference to the WordListsTableViewController
self.wordListTableViewController = segue.destinationViewController as! WordListsTableViewController
// set as it delegate
self.wordListTableViewController.delegate = self
}
}
And then you should be notified from the WordListsTableViewController, in the above example I assume the use of segues, but if you present the WordListsTableViewController you can use the same principle of keep a reference to the delegate class, like I show in the above example.
I do not apply any concept in code regarding the retain-cycles that can be happen in the use of delegates, but you can read more in my answer of this question about how to implement delegates correctly:
"fatal error: unexpectedly found nil while unwrapping an Optional value" while calling a protocol method
I strongly recommend you read more about the Delegate Pattern in this post:
How Delegation Works – A Swift Developer’s Guide
I hope this help you.
In your HomeViewController you have to set delegate to self:
override func viewDidLoad() {
super.viewDidLoad()
// get reference to your word lists table view controller
// if controller is your table view that should work
// controller.delegate = self
let wordLists = WordListsTableViewController....
// set up delegate
wordLists.delegate = self.
}

How do I assign an IBOutlet a value using an Initializer?

I am having difficulty with something that I feel like should be working, I am wondering if there are some things that I'm not understanding about initializing values of IBOutlets.
So I've created a class that has three IBOutlets. I then created initializers for those outlet variables. When I create a new instance of the class and pass it hard-coded values I get nil in return.
So I've println()'ed the incoming settingLabel value to make sure incoming parameter indeed carries a value, it comes back as "hello" as to be expected. Then I attempt to assign the value to the settingsLabel.text, however when I call println(settingsLabel.text) comes back as nil.
(Thread 1: EXV_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0))
My Question is:
What am I missing here and/or How do I assign an IBOutlet a value with an initializer?
This is my call to the initializer:
let cell = SettingCell(settingLabel: "hello", settingSwitch: true, timeSetting: 3)
This is my SettingCell.swift class:
class SettingCell: UITableViewCell {
#IBOutlet weak var settingsLabel: UILabel!
#IBOutlet weak var settingsSwitch: UISwitch!
#IBOutlet weak var timeSetting: UILabel!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
init(settingLabel: String, settingSwitch: Bool, timeSetting: NSNumber) {
println(settingLabel) // returns hello <--------------------
super.init(style: .Default, reuseIdentifier: "SwitchSettingCell")
settingsLabel.text = settingLabel
self.settingsSwitch.on = settingSwitch
self.timeSetting.text = timeSetting.description as String
println(settingsLabel.text) // returns nil <--------------------
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var cellDelegate: SettingCellDelegate?
#IBAction func handledSwitchChange(sender: UISwitch) {
self.cellDelegate?.didChangeSwitchState(sender: self, isOn:settingsSwitch!.on)
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
myStoryboard:
The IBOutlet keyword is just a way to tell Xcode that the view should be visible inside your storyboard. You can connect these outlets in your storyboard, and in doing so cause the variables to be instantiated & added as subviews from the storyboard instead of in code.
In your code you are creating your cells programmatically, not from a storyboard (indeed if you tried to create them from a storyboard your app would crash, since you haven't implemented init(coder:)). Because of this, your variables have no reason to be "outlets" and are not ever initialised. You must initialise them yourself, add them as subviews & setup constraints, all in code.
In your initialiser, your code is ineffectual because your variables are still nil. In fact, in your edit, you removed the optional chaining ?s which will have the effect of crashing your app, since the variables are force unwrapped, but are nil. This is the same reason println(settingsLabel.text) is crashing.
Edit:
Ok, so your issue is that you want to initialise your cells from the storyboard, but you're instead doing it programmatically by calling your custom initialiser. In tableView(_:cellForRowAtIndexPath:), you get your cells by dequeuing one from the reuse queue:
let cell = tableView.dequeueReusableCellWithIdentifier("SettingsCell", forIndexPath: indexPath) as! SettingCell
(This assumes you have given your cell prototype the identifier "SettingsCell" in the storyboard)
Whenever a view is created from a storyboard, the view's init(coder:) initialiser is used, so you must provide an implementation of this in your SettingCell subclass. Once the cell has been dequeued, the cell's outlets will be initialised and added as subviews of the cell.
So after dequeuing a cell, you can setup your labels and switch:
cell.settingsLabel.text = //...
cell.settingsSwitch.on = //...
cell.timeSetting.text = //...