So consider this case, i have a UIViewController that contains a simple UICollectionView, but the Delegate & DataSource protocols are separated NSObject's from the UIViewController.
It looks something like this
class MainCollctionViewDelegate: NSObject, UICollectionViewDelegate
class MainCollectionViewDataSrouce: NSObject, UICollectionViewDataSource
And i use them inside my UIViewController like this,
lazy var CVDelegate = MainCollctionViewDelegate()
lazy var CVDataSource = MainCollectionViewDataSrouce()
//MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
collectionView.registerCell(PlainCell.self) // register custom cell Nib into collection View.
collectionView.delegate = CVDelegate //Set Deleagte
collectionView.dataSource = CVDataSource // Set data Source
}
Is this approach going to cause any memory leaks in the future ? considering i will implement an injection to fill the data source of the CollectionView to be something like this in the future.
MainCollectionViewDataSrouce(with: Foo) // Foo is some data to populate the collection with.
Is there a better practice to this ? considering I'am trying to achieve
the minimum code writing (redundancy).
Note: this also applies for UITableViewDelegate & UITableViewDataSource
Is this approach going to cause any memory leaks in the future ?
Not right now.
Your memory graph will look like:
So here no memory cycles and no reasons to leak memory.
Important. If you add reference from DataSource / Delegate on your viewController, make sure it is weak reference, otherwise you will create memory cycle.
Note. You can add strong references from DataSource / Delegate on collectionView, since collectionView have weak references on dataSource and delegate. So no cycle as well
Side note
Better to register cells in data source, since "only" data source know what types of cell will be used.
Your question is rather vague, but in general, that is a very common practice. We use this pattern a lot in our company:
class MainCollectionViewController: UIViewController {
lazy var dataSource: UICollectionViewDataSource = self
lazy var delegate: UICollectionViewDelegate = self
static func with(dataSource: UICollectionViewDataSource, delegate: UICollectionViewDelegate) -> MainCollectionViewController {
let vc = MainCollectionViewController()
vc.dataSource = dataSource
vc.delegate = delegate
return vc
}
}
extension MainCollectionViewController: UICollectionViewDataSource {
// code
}
extension MainCollectionViewController: UICollectionViewDelegate {
// code
}
The two primary uses are for unit testing and for passing data to the view controller. The tester can inject custom data source and delegate at test time:
let testVC = MainCollectionViewController.with(dataSource: ..., delegate: ...)
// do test
Or passing data to it:
// In another view controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationVC = segue.destination as? MainCollectionViewController {
destinationVC.dataSource = ...
destinationVC.delegate = ...
}
}
As to memory leak, this pattern is generally safe but obviously someone will run into a memory problem once in a while. Your mileage may vary.
Related
My question is very simple, I just want to understand why some people using extension for those delegate methods of UITableViewController? Why not directly code inside the controller? What I mean is, I saw people doing this:
class MyTableViewController: UITableViewController {
// bla bla
}
extension MyTableViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
}
...
}
What is the benefit of having the delegate methods implementation in extension? Why not just put the code inside MyTableViewController without extension? If it is a convention, then, why? What is good to do so & what is bad not doing it?
Today Maintainable of our code is more important than Performance
With this in mind, more programmers search to accomplish this task with extravagant stratagems. Using extension is one of them.
With an extensions you could make your code more readable.
But this trick is really helpful?
Single responsibility principle affirm that an object should be responsible of a single task.
In our ViewControllers we usually put so many things like TableView, IBAction, CollectionView and so on...
So, how can I follow the SRP without using the extension?
Just create a new object which will be delegate to manage, for example, the TableView.
class TableController: NSObject {
var dataSource: [Any]
init(dataSource: [Any]) {
super.init()
self.dataSource = dataSource
}
}
// here extension could help you to make your code more readable
extension TableViewController: UITableViewDataSource {
/* the TableViewDataSource method */
}
So, now, in your ViewController you can do this:
class ViewController: UIViewController {
#IBOutlet var tableView: UITableView!
var tableController: TableController!
override func viewDidLoad() {
super.viewDidLoad()
self.tableController = TableController.init(dataSource: [1,2,3])
self.tableView.dataSource = self.tableController
}
}
Hope this help you ;)
UIViewController generally contains many functions like IBOutlets, Private helper functions, IBActions and ViewController LifeCycle methods etc. If you put all your code in ViewController it becomes little difficult to read and hard to maintain as we add more functionality. Extensions is one of the ways to separate the logical functionality in separate files so that it's easy to read and maintain the code and modify when needed. Using extension to put all the functions which confirms to some protocol like UITableViewDataSource is one way.
One benefit of using extension is that you create multiple files and add the functions like in normal classes.
Disadvantage is you can't access private variables from the classes also can't declare constants in extensions.
If you prefer to keep the code in same file then using //MARK: is another way to group similar functionalities
//MARK: ViewController LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
}
For some reason the delegate method is not being called in the main View Controller. I was looking for another answers here, but non of them were helpful for me. Am I missing something here? (I shortened my original code for simplicity sake)
Main View Controller:
class VC: ParserDelegate {
var dataSource = Parser()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
dataSourse.loadAndParse()
}
func didReceiveDataUpdates(store: [WeatherModel]) {
print("Delegate method triggered.")
}
}
Protocol:
protocol ParserDelegate: class {
func didReceiveDataUpdates(store: [WeatherModel])
}
My delegate class:
class Parser {
weak var delegate: ParserDelegate?
func loadAndParse() {
var store = [WeatherModel]()
// Doing something
delegate?.didReceiveDataUpdates(store: store)
}
}
The delegate pattern is being applied correctly here, but one thing that might go wrong here: In your main View Controller you are instantiating a new Parser object and store it in „dataSource“:
var dataSource = Parser()
And when setting your main View Controller as its delegate
dataSource.delegate = self
your main View Controller gets notified as the delegate of this new instance you just created. That means: If an instance of your Parser() class jumps into (assure with debugger, if it actually does)
loadAndParse()
it might be another object and so this parser object has no actual delegate. If this is the issue here, you might consider and outlet in order to be able to talk to this specific Parser() class directly. Hope this helps.
You can also edit this line:
from:
dataSource.delegate = self
dataSourse.loadAndParse()
to:
dataSource.delegate = self
dataSource.loadAndParse()
Delegates an important concept in ObjC/SWIFT or any other coding language. I know that delegates are used to pass messages from one class to another class especially when we want to pass message back to a view controller from where we have just moved to some other view controller.
I was searching for more technical answer and searched a lot about this, and here is what I got what I feel might be the exact answer -
By the rules of MVC, we need a method to return a value. Where in a
called instance can we go back to the class calling it? With an
encapsulated class we can’t. There is no way to send that revised
model back to the original controller without breaking encapsulation
or MVC. The new view controller does not know anything about the class
that called it. We look stuck. If we try to make a reference directly
to the calling controller, we may cause a reference loop that will
kill our memory. Simply put, we can’t send things backwards.
But the explanation says something about
Where in a
called instance can we go back to the class calling it? With an
encapsulated class we can’t. There is no way to send that revised
model back to the original controller without breaking encapsulation
or MVC.
So exactly what does this para mean. Can any one please explain this in a more simple way taking the following code as reference -
VC2 -
import UIKit
protocol myDelegate : class
{
func sendItems(name:NSString)
}
class EnterViewController: UIViewController
{
weak var delegate: myDelegate?
#IBOutlet weak var nameTextfield: UITextField!
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.
}
#IBAction func sendData(sender: AnyObject)
{
delegate?.sendItems(nameTextfield.text!)
self.navigationController?.popViewControllerAnimated(true)
}
VC2
import UIKit
class DisplayViewController: UIViewController,myDelegate
{
#IBOutlet weak var nameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func sendItems(name: NSString) {
self.nameLabel.text = name as String
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
let destinationVC = segue.destinationViewController as! EnterViewController
if segue.identifier == "enterDetail"
{
destinationVC.delegate = self
}
}
}
Thanks.
In simple terms, Delegate is a representative of Class which works on behalf of class. If we compare in real world, delegates to foreign countries go to represent their government and have all controls and powers. Similarly, here delegates has all the control that an object of class will have and working on behalf of Class.
Now delegate in specific is an object assigned by class to notify the event. This can be acheived by NSNotification too. But the difference is Delegates can intercept the event but NSNotification cant.
Here in your code:
You have assigned DestinationVC's delegate to DisplayViewController.
Now DisplayviewController will notify all the event whatever you want to notify to Class DestinationVC and also intercept in between event.
In your case you are calling sendItems of DestinationVC.
I am sorry for my bad explanation but I guess you would have get the basic idea.
I have a UICollectionView with a CollectionReusableView header. I want to pass a string from the collecitonview to the header, so that the header knows which data to load based on the string. I am trying to use delegates/protocols to do this, but keep getting "unexpectedly found nil while unwrapping an optional value." Here is my code:
protocol UserToQuery {
func thisUser(x: String)
}
class Profile: UICollectionViewController {
var ownProfile = true
var delegate:UserToQuery?
override func viewDidLoad() {
super.viewDidLoad()
if self.ownProfile == true {
let username = PFUser.currentUser()?.username
self.delegate!.thisUser(username!)
}
}
}
And here is the code for the Header view:
class ProfileHeader: UICollectionReusableView, UserToQuery {
var id1 = String()
var controller = Profile()
override func awakeFromNib() {
print(id1)
controller.delegate? = self
}
func thisUser(x: String) {
self.id1 = x
getProfileInfo()
}
func getUserData() {
// code here uses the id1 value to get data
}
}
My understanding of delegates/protocols is this: if you want to pass data (i.e., string), to another view, you make the view that receives the string conform to a protocol. This protocol includes a function that is used to pass the string, and when that function is called, it notifies the other view that the string is now available for use, and then you code what you want and use the string. Is that accurate?
In ProfileHeader, you have a variable, controller, which is creating a new instance of Profile, which is NOT the Profile view controller from your storyboard. This is why self.delegate! is nil in Profile.viewDidLoad().
I am going to make the assumption that ProfileHeader is a view in the Profile view controller. In your viewDidLoad, you should set the delegate to the ProfileHeader. See the example code below (I assume an outlet for the ProfileHeader view):
EDIT: ProfileHeader is not an outlet, as mentioned in the comments. Updated my answer to reflect that.
class Profile: UICollectionViewController {
var ownProfile = true
var delegate:UserToQuery?
override func viewDidLoad() {
super.viewDidLoad()
// Set the delegate!
self.delegate = ProfileHeader()
if self.ownProfile == true {
let username = PFUser.currentUser()?.username
// Delegate won't be nil now
self.delegate!.thisUser(username!)
}
}
}
}
As a general flow, the view controller should keep references to the view, not the other way around. So remove the controller property from your ProfileHeader view. The view shouldn't care what view controller is controlling it.
You have some misunderstandings about protocol/delegate, but it’s normal when you start iOS development.
First of all, why does the app crash :
The variable delegate is an optional UserQuery. It’s okay for a delegate to be optional, but it’s never set in your code, so when you call :
self.delegate!.thisUser(username!)
you try to force unwrapping a nil variable, which results in the crash.
Protocols
Now, let’s talk about the protocol/delegate relationship.
You have an UICollectionViewController subclass, which embeds an UICollectionView object. This UICollectionView will be contains a mix of header, footer and cell. Your ProfileHeader class will thus be displayed within your UICollectionView.
In order to populate an UICollectionView, you don’t need to create your own protocol : there are already two protocols for this :
UICollectionViewDataSource is the main protocol to conforms to, because it allows you to populate the collection view
UICollectionViewDelegate is used for further customization of your tableview, i.e. customizing the appearance and handling events.
Since your Profile class inherits from UICollectionViewControlleryou don’t have to named these protocols after your class name since UICollectionViewController already conforms to these protocols as written in Apple docs
You will have to override the delegate and protocol methods in order to display some data. My advice is, before using headers and footers, to use only UICollectionViewCell objects for start easily.
By overriding the method -collectionView:numberOfItemsInSection: and - collectionView:cellForItemAtIndexPath:, you will be able to populate the collection view.
I want to update the label in the DetailViewController everytime I selected a tableRow in the MasterViewController. To achieve this, I designed a delegate, which I have in the MasterVC
protocol TestTableViewControllerDelegate {
func selectedRow(selectedCar : Car)
}
class TestTableViewController: UITableViewController {
...
var delegate : TestTableViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = DetailViewController()
The delegate works just fine, (it is implemented correctly in the DetailVC), it can pass values from TestTableVC to DetailVC and also correctly do println(), which prints a new Car.model String to the console every time I select a row in the TTVC.
The DetailVC looks like this (shortened):
class DetailViewController: UIViewController, TestTableViewControllerDelegate {
#IBOutlet var textLabel: UILabel!
var theCar : Car? {
didSet(newCar) {
refreshUI()
}
}
override func viewDidLoad() {
super.viewDidLoad()
refreshUI()
}
func selectedRow(selectedCar : Car) {
theCar = selectedCar
refreshUI()
}
func refreshUI() {
textLabel?.text = theCar!.model
}
}
I can achieve any kind of action with my delegate, expect for refreshing the UI. I have tried numerous ways, this is my latest attempt. Before that, I tried setting the textLabel's text property directly within the delegate method, didn't work. This problem only occurs when working with the UI-elements. I know it has something to do with the view not being loaded yet, but why does my refreshUI() function not work at all?
I am still a beginner, so any tip or help would be much appreciated!
A workaround I've used is to cerate a properly in the delegate and pass the value to it instead of the UI element. When the view loads I update the label's text properly with the value of the delegates property. I would think there's a better way to do this (I'm new to programming) but this is the best soultion I've come up with so far. Will update with sample code soon.