Change property of object in another view controller - ios

I have a UITabBarController (as initial view controller) which checks connectivity status of the device. Everytime the connectivity status changes, a checkmark in a child UITableViewController cell (.accessoryType) should be set (.checkmark) or removed (.none)
Code in Tab Bar Controller:
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
let tvc = InfoTableViewController()
if path.status == .satisfied {
// set .checkmark in UITableViewController
let cell = tvc.statusOnlineCell
print("cell :", cell)
} else {
// set .none in UITableViewController
}
}
let queue = DispatchQueue.global(qos: .background)
monitor.start(queue: queue)
Outlet in UITableViewController:
#IBOutlet weak var statusOnlineCell: UITableViewCell!
I can change the accessory type from inside the UITableViewController class using .checkmark and .none.
statusOnlineCell.accessoryType = .checkmark
statusOnlineCell.accessoryType = .none
All fine so far!
However as soon as I try to access the UITableView.statusOnlineCell from UITabBar, I get nil. Hence, I cannot change its property outside the UITableViewController.
I saw 3 possible approaches:
A global variable, which reflects the online status. I could use the UITableView.viewDidAppear() method to change the statusOnlineCell accessory type. This works, but only if UITableView is not shown (only if another than UITableView is shown). If the UITableView is shown and I change the connectivity status, the view is not reloaded and I didn't find any way to achieve this. Is this possible?
Find a possibility to change the accessory type of UITableView.statusOnlineCell from the UITabBarController. Accessing the outlook returned in nil. Why is that? On top, after the accessory type would have changed, I would need to reload the view (for the case that the UITableView was active while changing connectivity status).
Is there any kind of (unknown to me) method which fires, when an object's property changed (à la needReload())? This would be too good to be true I believe.
To summarize - I need code to change the accessory type of a tableview cell, depending on the connectivity status, even whith this tableview being visible.
I read some tutorials and stackexchange articles, Google, ... but none did the job.
This was my top candidate, but I didn't manager to apply these examples to my situation.
https://learnappmaking.com/pass-data-between-view-controllers-swift-how-to/#back-delegation
I didn't want to use notifications since not really appropriate.
Any hint would be sufficient. Thanks in advance.
----- EDIT (14:52 UTC) ----- (requested by #vadian)
I added the (testing) code in the UITabBarController. The result of cell shows nil. So I cannot directly address the cell in UITableView from UITabBarController.
The UITableView doesn't have any related code yet since I directly address the property statusOnlineCell from UITabBarController in order to change its accessory type.
This approach is option 2. (of my 3 possibilities mentioned above).

I didn't find a solution, but a workaround.
Why no solution : the cell object was always nil if called from another class. Hence no chance to change it's property to .checkmark or .none. This was basically my key problem and unknown to me.
The workaround : a delegate! I declared the class, holding the cell, as delegate for the connectivity status change. Like that, the checkmark gets set or is removed from the cell, independantly whether the page is in view or not.
This runs when the app starts (UITabBarController):
protocol StatusOnlineChangedDelegate {
func updateStatusOnline(_ online: Bool)
}
class TabBarController: UITabBarController {
var socDelegate: StatusOnlineChangedDelegate?
override func viewDidLoad() {
super.viewDidLoad()
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
if path.status == .satisfied {
self.socDelegate?.updateStatusOnline(true)
} else {
self.socDelegate?.updateStatusOnline(false)
}
}
let queue = DispatchQueue.global(qos: .background)
monitor.start(queue: queue)
}
}
The UITableViewController holding the cell looks like this:
class InfoTableViewController: UITableViewController, StatusOnlineChangedDelegate {
#IBOutlet weak var statusOnlineCell: UITableViewCell!
override func viewDidLoad() {
super.viewDidLoad()
TabBarController().socDelegate = self
}
func updateStatusOnline(_ online: Bool) {
DispatchQueue.main.async {
self.statusOnlineCell.accessoryType = (online ? .checkmark : .none)
}
}
}
Runs perfectly as desired.

Related

Why am I getting nil for tableview outlet?

When the callback for the TaskListDataSource gets called it reloads both the todayVC and the reviewVC because they are UITableViewControllers. However the plannerVC is not and the tableview property is an outlet.
#IBOutlet weak var tableView: UITableView!
Why is it that when the callback runs it crashes saying it is nil. If I am somehow able to scroll across in the page view however and and view the plannerVC it will never crash as the tableview has been loaded into memory. But why doesn't it do it initially?
override func viewDidLoad() {
super.viewDidLoad()
let taskListDataSource = TaskListDataSource {
self.todayVC.tableView.reloadData()
self.plannerVC.tableView.reloadData()
self.reviewVC.tableView.reloadData()
}
todayVC = storyboard!.instantiateViewController(identifier: "TodayViewController", creator: { coder in
return TodayViewController(coder: coder, taskListDataSource: taskListDataSource)
})
plannerVC = storyboard!.instantiateViewController(identifier: "PlannerViewController", creator: { coder in
return PlannerViewController(coder: coder, taskListDataSource: taskListDataSource)
})
reviewVC = storyboard!.instantiateViewController(identifier: "ReviewViewController", creator: { coder in
return ReviewViewController(coder: coder, taskListDataSource: taskListDataSource)
})
addVC = storyboard!.instantiateViewController(identifier: "AddViewController")
setViewControllers([todayVC], direction: .forward, animated: false)
dataSource = self
print(plannerVC.tableView) // Console is printing nil
}
When you call instantiateInitialViewController(creator:), the UIViewController is initiated, but its view (and all subviews, including then all the IBOutlet) aren't loaded in memory.
So when, you try to do self.someIBoutlet (in your case self.plannerVC.tableView.reloadData(), it crashes.
A solution, would be to force the view to load, with loadViewIfNeeded().
Since loading the view can be heavy, it's usually used when the ViewController will be shown shortly after (for instance, in a didSet of some property that access outlet in it, because it will be shown on screen in a few instants, so the view will be loaded anyway, just a few moment after).
Since you are loading 3 UIViewController, could it be that you aren't showing them, but prematurely loading them?
If that's the case, you might rethink your app architecture (all your UIViewController don't need to be initialized and in memory, and less to have their view loaded).
Still, you can check beforehand if the view has been loaded, and that you can access the outlets with isViewLoaded.
I'dd add for that a method in PlannerVC:
func refreshData() {
guard isViewLoaded else { return }
tableView.reloadData()
}
Side note, it could be a protocol (and even more, complexe, like adding var tableView { get set }, and have a default implementation of refreshData(), but that's going further, not necessary)...
protocol Refreshable {
func refreshData()
}
let taskListDataSource = TaskListDataSource {
self.todayVC.refreshData()
self.plannerVC.refreshData()
self.reviewVC.refreshData()
}
Side note, I would check if there isn't memory retain cycles, I would have use a [weak self] in the closure of TaskListDataSource, and also would have made it a property of the VC.

buttons and labels resetting when scrolling through collection view

I have a collection view in which each cell possess the ability to be interacted with by the user. Each cell has a like button and a number of likes label. When the button is pressed, the button should turn cyan, and the label (which holds the number of likes) should increment. This setup currently works. However, when I scroll through the collection view and scroll back, the button reverts to its original color (white) and the label decrements down to its original value. I have heard of an ostensibly helpful method called prepareForReuse(), but perhaps I'm not using it correctly. Here is my code:
Here is the array which holds all the cells
var objects = [LikableObject]()
Here is the class definition for these objects
class LikableObject {
var numOfLikes: Int?
var isLikedByUser: Bool?
init(numOfLikes: Int, isLikedByUser: Bool) {
self.numOfLikes = numOfLikes
self.isLikedByUser = isLikedByUser
}
}
Mind you, there is more functionality present in this object, but they are irrelevant for the purposes of this question. One important thing to be noted is that the data for each cell are grabbed using an API. I'm using Alamofire to make requests to an API that will bring back the information for the numOfLikes and isLikedByUser properties for each cell.
Here is how I load up each cell using the collection view's delegate method:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ObjectCell", for: indexPath) as! ObjectCell
cell.configureCell(
isLikedByUser: objects[indexPath.row].isLikedByUser!,
numOfLikes: objects[indexPath.row].numOfLikes!,
)
return cell
}
The ObjectCell class has these three fields:
var isLikedByUser: Bool?
#IBOutlet weak var numOfLikes: UILabel!
#IBOutlet weak var likeBtn: UIButton!
And that configureCell() method, which belongs to the cell class, is here:
public func configureCell(numOfLikes: Int, isLikedByUser: Bool) {
self.isLikedByUser = isLikedByUser
self.numOfLikes.text = String(numOfLikes)
if isLikedByUser {
self.likeBtn.setFATitleColor(color: UIColor.cyan, forState: .normal)
} else {
self.likeBtn.setFATitleColor(color: UIColor.white, forState: .normal)
}
}
And lastly, the prepareForReuse() method is here:
override func prepareForReuse() {
if isLikedByUser! {
self.likeBtn.setTitleColor(UIColor.cyan, for: .normal)
} else {
self.likeBtn.setTitleColor(UIColor.white, for: .normal)
}
}
This doesn't work. And even if it did, I still don't know a way to keep the numOfLikes label from decrementing, or if it should anyway. I'm speculating that a big part of this problem is that I'm not using the prepareForReuse() method correctly... Any help is appreciated, thank you.
prepareForReuse is not the place to modify the cell, as the name states, you "only" have to prepare it for reuse. if you changed something (for example isHidden property of a view), you just have to change them back to initial state.
What you should do though, you can implement didSet for isLikedByUser inside the cell, and apply your modifications to likeBtn in there. (this is of-course the fast solution)
Long solution: It's an anti-pattern that your cell has a property named isLikedByUser, TableViewCell is a View and in all architectures, Views should be as dumb as they can about business logic. the right way is to apply these modifications in configure-cell method which is implemented in ViewController.
If you feel you'll reuse this cell in different viewControllers a lot, at least defined it by a protocol and talk to your cell through that protocol. This way you'll have a more reusable and maintainable code.
Currently all of this is good , the only missing part is cell reusing , you have to reflect the changes in the number of likes to your model array
class ObjectCell:UICollectionViewCell {
var myObject:LikableObject!
}
In cellForRowAt
cell.myObject = objects[indexPath.row]
Now inside cell custom class you have the object reflect any change to it , sure you can use delegate / callback or any observation technique
The prepareForResuse isn't needed here.
You do need to update the model underlying the tableview. One way to verify this is with mock data that is pre-liked and see if that data displays properly.

Need help setting UISwitch in custom cell (XIB, Swift 4, Xcode 9)

Successes so far: I have a remote data source. Data gets pulled dynamically into a View Controller. The data is used to name a .title and .subtitle on each of the reusable custom cells. Also, each custom cell has a UISwitch, which I have been able to get functional for sending out both a “subscribe” signal for push notifications (for a given group identified by the cell’s title/subtitle) and an “unsubscribe” signal as well.
My one remaining issue: Whenever the user "revisits" the settings VC, while my code is "resetting" the UISwitches, it causes the following warnings in Xcode 9.2:
UISwitch.on must be used from main thread
UISwitch.setOn(_:animated:) must be used from main thread only
-[UISwitch setOn:animated:notifyingVisualElement:] must be used from main thread
The code below "works" -- however the desired result happens rather slowly (the UISwitches that are indeed supposed to be "on" take a good while to finally flip to "on").
More details:
What is needed: Whenever the VC is either shown or "re-shown," I need to "reset" the custom cell’s UISwitch to "on" if the user is subscribed to the given group, and to "off" if the user is not subscribed. Ideally, each time the VC is displayed, something should reach out and touch the OneSignal server and find out that user’s “subscribe state” for each group, using the OneSignal.getTags() function. I have that part working. This code is in the VC. But I need to do it the right way, to suit proper protocols regarding threading.
VC file, “ViewController_13_Settings.swift” holds a Table View with the reusable custom cell.
Table View file is named "CustomTableViewCell.swift"
The custom cell is called "customCell" (I know, my names are all really creative).
The custom cell (designed in XIB) has only three items inside it:
Title – A displayed “friendly name” of a “group” to be subscribed to or unsubscribed from. Set from the remote data source
Subtitle – A hidden “database name” of the aforementioned group. Hidden from the user. Set from the remote data source.
UISwitch - named "switchMinistryGroupList"
How do I properly set the UISwitch programmatically?
Here is the code in ViewController_13_Settings.swift that seems pertinent:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! CustomTableViewCell
// set cell's title and subtitle
cell.textLabelMinistryGroupList?.text = MinistryGroupArray[indexPath.row]
cell.textHiddenUserTagName?.text = OneSignalUserTagArray[indexPath.row]
// set the custom cell's UISwitch.
OneSignal.getTags({ tags in
print("tags - \(tags!)")
self.OneSignalUserTags = String(describing: tags)
print("OneSignalUserTags, from within the OneSignal func, = \(self.OneSignalUserTags)")
if self.OneSignalUserTags.range(of: cell.textHiddenUserTagName.text!) != nil {
print("The \(cell.textHiddenUserTagName.text!) UserTag exists for this device.")
cell.switchMinistryGroupList.isOn = true
} else {
cell.switchMinistryGroupList.isOn = false
}
}, onFailure: { error in
print("Error getting tags - \(String(describing: error?.localizedDescription))")
// errorWithDomain - OneSignalError
// code - HTTP error code from the OneSignal server
// userInfo - JSON OneSignal responded with
})
viewWillAppear(true)
return cell
}
}
In the above portion of the VC code, this part (below) is what is functioning but apparently not in a way the uses threading properly:
if OneSignalUserTags.range(of: cell.textHiddenUserTagName.text!) != nil {
print("The \(cell.textHiddenUserTagName.text!) UserTag exists for this device.")
cell.switchMinistryGroupList.isOn = true
} else {
cell.switchMinistryGroupList.isOn = false
}
It's not entirely clear what your code is doing, but there seems to be a few things that need sorting out, that will help you solve your problem.
1) Improve the naming of your objects. This helps others see what's going on when asking questions.
Don't call your cell CustomTableViewCell - call it, say, MinistryCell or something that represents the data its displaying. Rather than textLabelMinistryGroupList and textHiddenUserTagName tree ministryGroup and userTagName etc.
2) Let the cell populate itself. Make your IBOutlets in your cell private so you can't assign to them directly in your view controller. This is a bad habit!
3) Create an object (Ministry, say) that corresponds to the data you're assigning to the cell. Assign this to the cell and let the cell assign to its Outlets.
4) Never call viewWillAppear, or anything like it! These are called by the system.
You'll end up with something like this:
In your view controller
struct Ministry {
let group: String
let userTag: String
var tagExists: Bool?
}
You should create an array var ministries: [Ministry] and populate it at the start, rather than dealing with MinistryGroupArray and OneSignalUserTagArray separately.
In your cell
class MinistryCell: UITableViewCell {
#IBOutlet private weak var ministryGroup: UILabel!
#IBOutlet private weak var userTagName: UILabel!
#IBOutlet private weak var switch: UISwitch!
var ministry: Ministry? {
didSet {
ministryGroup.text = ministry?.group
userTagName.text = ministry?.userTag
if let tagExists = ministry?.tagExists {
switch.isEnabled = false
switch.isOn = tagExists
} else {
// We don't know the current state - disable the switch?
switch.isEnabled = false
}
}
}
}
Then you dataSource method will look like…
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! MinistryCell
let ministry = ministries[indexPath.row]
cell.ministry = ministry
if ministry.tagExists == nil {
OneSignal.getTags { tags in
// Success - so update the corresponding ministry.tagExists
// then reload the cell at this indexPath
}, onFailure: { error in
print("Error")
})
}
return cell
}

xcode 7 swift found nil while unwrapping optional in a table view cell

I'm sorry to be asking yet another "found nil unwrapping optional in TableViewCell" posting, but after hours of debugging and reading other such posts here, I'm still stuck. I think I've covered all of the usual mistakes, but still can't get things to work.
As a starting point, everything works fine as long as I use the default label in the cell, referenced with "cell.textLabel!.text = mystring." And I have another case where I customized the prototype cell with an image and a label, and that worked fine too.
In the current case, I have a normal UIViewController, with some other views and a UITableView embedded. The normal UIViewController is set as the delegate for the cells.
Here's the SettingsCell.swift code. Pretty simple. The little dot on the left of the Outlet in xcode is solid, showing that the outlet is properly connected to the CellTwo label on my prototype table cell.
class SettingsCell: UITableViewCell {
#IBOutlet weak var CellTwo: UILabel!
override init(style: UITableViewCellStyle, reuseIdentifier reuseID: String?) {
super.init(style: style, reuseIdentifier: reuseID)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In the corresponding SettingsVC.swift view controller, here is the dequeueCell code that takes the nil fault on trying to set the cell.CellTwo value.
The cells display fine in the table view, but only the default textLabel.txt that is part of the UILabel shows up. My custom label does not show at all.
func tableView(
tv: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tv.dequeueReusableCellWithIdentifier("SettingsCell") as! SettingsCell
let row = indexPath.row
cell.textLabel!.text = SettingsNames[row]
// the commented out line below crashes on a nil unwrapped optional
// cell.CellTwo!.text = "Blah"
if let b = cell.CellTwo {
b.text = "Blah"
}
return cell
}
With the little "let b =" optional unwrap nil protection, the app does not crash, but my second label doesn't get set either. Finally, consider registration of the class.
Many examples exist on the net (without using prototypes) where you register the class, and I've done that before successfully. And I've done an example where I built a custom prototype cell, and that one worked fine too. But... I think all those examples were using a UITableViewController, not a normal UIViewController + embedded UITableView. Maybe that has something to do with it.
Anyhow, here is the init / viewDidLoad code in the UIViewController that contains the UITableView. As you can see from the code, the UIViewController claims the datasource and delegate responsibilities, and sure enough, the table rows show up and work fine.
I've tried registration both ways -- without the code registration, and with the code registration line. But it doesn't make any difference at all - my code still crashes on the nil reference to my custom label in the cell.
class SettingsVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
SettingsTV.dataSource = self
SettingsTV.delegate = self
// I tried with this line in and out, no difference, the crash still happens
//SettingsTV.registerClass(SettingsCell.self, forCellReuseIdentifier: "SettingsCell")
SettingsTV.estimatedRowHeight = 70
}
It's like my custom label doesn't get created or initialized somehow. But I can't explain it. Any ideas? Thanks
The answer to my issue was in the initialization of the SettingsCell shown in the first block of code above. That initialization sequence was copied from an example that worked fine, but that DID NOT USE THE STORYBOARD. Instead, that example just limited itself to using the default textLabel.text fields in the UITableViewCell definition.
To solve the problem, I just kept going back through my examples trying to recreate the problem in examples that already worked. Here's the key.
If you use the storyboard with a prototype cell, the table cell initialization cannot look like the first block in this posting!! That style only works for non-storyboard cells.
Instead, it should look like this one below. Notice that I commented out the default code that xcode inserts into the file when it is created. So you can basically have an empty class for the cell, as long as you have places for the Outlets.
class SettingsCell: UITableViewCell {
#IBOutlet weak var XXXLabel: UILabel!
#IBOutlet weak var CellTwo: UILabel!
// 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
// }
}
A second thing that showed up was that the two labels on the storyboard were placed at either end of the any-any size storyboard. And my constraints put the right hand label off the edge of the simulated device. I happened to rotate the device in the simulator, and presto! there was the second label. I should use the previewer more regularly. :-)
Have you tried defining your custom cell in a seperate XIB instead of using prototype cells in the tableview? It is pretty much the same as what you did but it just has another XIB file out of the storyboard. To create the XIB:
File -> New File -> iOS -> User Interface ->Empty
Drag a UITableViewCell to the XIB and customize accordingly. Set its class to SettingsCell and wire up the labels, etc... to your SettingsCell class
Then in your VC you register it like this:
let nibName = UINib(nibName: "SettingsCell", bundle:nil)
self.SettingsTV.registerNib(nibName, forCellReuseIdentifier: "SettingsCell")
Another part of the answer is in the contents of the NSCoder required init block shown at the top of this post. That block was copied from a working example that did NOT use a storyboard prototype cell. So I often took a fatal error when I tried that with my prototype tableview cell.
Here is what that block SHOULD (must) look like if you are using a prototype cell. The required NSCoder must call the super.init, like so:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//fatalError("init(coder:) has not been implemented")
}
It's odd that when xcode auto-fixes the NSCoder issue by injecting a template NSCoder required init? function, it injects one that will break with prototype cells. Why not add the super.init(..) line instead of the fatal error line? Probably there's a reason, but not one that I can see.
Anyhow, all this works for me now, even with a prototype storyboard table view. So I think that's all the questions/issues resolved for this one.

Passing data with delegates/protocols

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.

Resources