ReloadData with Protocol on custom cell with Collectionview? - ios

I attempted a bunch of answers on here with no luck.
I am attempting to reload my collectionview within my custom cell after fetching data from firebase. I am unable to even get the print statement to show from the custom cell. Not sure what I'm doing wrong.
On my ViewController I have the following delegate and call, I made a button to test to see if the delegate print statement on my custom cell will appear but no luck.
protocol DiscoverControllerDelegate {
func didFetchData()
}
class DiscoverController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId = "cellId"
let headerId = "headerId"
let database = FirebaseData.sharedInstance
var delegate: DiscoverControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
title = "Discover"
collectionView?.backgroundColor = .white
collectionView?.register(CategoriesCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.register(HeaderCell.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerId)
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Refresh", style: .plain, target: self, action: #selector(reloadData))
database.retrieveData {
print("fetching data")
self.delegate?.didFetchData()
}
}
#objc func reloadData () {
print("attemping to reload")
self.delegate?.didFetchData()
}
*edit to add on my override init
On my custom cell:
class CategoriesCell: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, DiscoverControllerDelegate {
let itemCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = .clear
return collectionView
}()
func didFetchData() {
print("RELOADING NOW!")
itemCollectionView.reloadData()
}
override init(frame: CGRect) {
super.init(frame: frame)
itemCollectionView.dataSource = self
itemCollectionView.delegate = self
itemCollectionView.register(ItemCell.self, forCellWithReuseIdentifier: cellId)
itemCollectionView.reloadData()
createLayout()
}

Many things wrong here. The reason "RELOADING NOW!" never logs is because you never set the delegate in your view controller. If you inspect self.delegate in DiscoverController it is probably nil. However, your entire setup right now seems flawed unless I'm misunderstanding what you're doing.
What does your UI look like? Why does CategoriesCell have a UICollectionView property? It's itemCollectionView property doesn't even have a datasource or delegate set and its not an #IBOutlet yet you're calling reloadData() on it. Not sure what it's even displaying or what you're tyring to do.
I feel like there is probably a lot more wrong here than just a single thing. You probably want to go through some UITableView and UICollectionView tutorials to learn about delegates and datasources in iOS.

I dont know about collection view but I use similar protocol structures with a table view. They are not much different. your protocol should be in the cell class not in the controller.
I've gone through the steps for a table view in the following post:
Firebase update
Maybe you can adapt it for your needs...

Related

CollectionView doesn't show any CollectionViewCell

I've created a generic UIView class which contains a UICollectionView inside of it. Just as below. (Class Below also handles the protocols of UICollectionView with Default values)
class MyCollectionView: BaseView<CollectionViewModel> {
private lazy var myCollectionView: UICollectionView = {
let temp = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewLayout()) // setting initial collectionView
temp.translatesAutoresizingMaskIntoConstraints = false
temp.delegate = self
temp.dataSource = self
temp.register(CollectionViewCell.self, forCellWithReuseIdentifier: CollectionViewCell.identifier)
temp.clipsToBounds = true
return temp
}()
}
I've created an instance of MyCollectionView(class above), and added as subview to the MainViewController(Class Below). So doing that made me show a MyCollectionView as a subview of MainViewController. I've accomplished so far.
class MainViewController: UIViewController {
private lazy var collectionView: MyCollectionView = {
let temp = MyCollectionView()
temp.translatesAutoresizingMaskIntoConstraints = false
temp.backgroundColor = .black
return temp
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
setUpConstraintsAndViews()
// Do any additional setup after loading the view.
}
Later on I tried to make UICollectionViewCell class and register that to myCollectionView. But still I can not see any cells on my screen. What might I could be missing?
Your MyCollectionView class is not a UICollectionView. It does not have a subview of a UICollectionView it has a private variable myCollectionView that creates a collection view.
As far as I can tell you're adding an instance of MyCollectionView as a subview of your MainViewController. I don't see how that adds a UICollectionView to your view MainViewController.
How is the consumer of your MyCollectionView supposed to get to the collection view that it owns?

How to call function when collection view is reloaded?

I have a collection view and want to run some things when it is reloaded by the user. This reloading is caused by the user dragging down on the view until the reload icon comes out. Putting this function at anything else other than when the user does an interaction to reload the collection, will make this an unfeasible solution for me.
What about the following solution. I implemented it for collection-, table- and scroll views, in case other people have similar issues:
final class ScrollViewRefreshDemo: UIViewController {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
let tableView = UITableView()
let scrollView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
attachRefreshListener(to: collectionView)
attachRefreshListener(to: tableView)
attachRefreshListener(to: scrollView)
}
func attachRefreshListener(to scrollView: UIScrollView) {
scrollView.refreshControl?.addTarget(self, action: #selector(scrollViewDidRefresh), for: .valueChanged)
}
#objc
func scrollViewDidRefresh() {
// Execute any task while the scroll view is refreshing
}
}

Get the frame of a UIStackView subViews

I have created a UIStackView in IB which has the distribution set to Fill Equally. I am looking to get the frame for each subView but the following code always returns (0, 0, 0, 0).
class ViewController: UIViewController {
#IBOutlet weak var stackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
let pView = UIView()
let sView = UIView()
pView.backgroundColor = UIColor.red
sView.backgroundColor = UIColor.orange
stackView.addArrangedSubview(pView)
stackView.addArrangedSubview(sView)
}
override func viewDidLayoutSubviews() {
print(stackView.arrangedSubviews[0].frame)
print(stackView.arrangedSubviews[1].frame)
}
}
I would think that a stack view set to fill equally would automatically set the calculate it.
Any help would be appreciated.
After reading over your code I think this is just a misunderstanding of viewDidLayoutSubviews(). Basically it is called when all the views that are descendants of the main view have been laid out but this does not include the subviews(descendants) of these views. See discussion notes from Apple.
"When the bounds change for a view controller's view, the view adjusts the positions of its subviews and then the system calls this method. However, this method being called does not indicate that the individual layouts of the view's subviews have been adjusted. Each subview is responsible for adjusting its own layout."
Now there are many ways to get the frame of the subviews with this being said.
First you could add one line of code in viewdidload and get it there.
override func viewDidLoad() {
super.viewDidLoad()
let pView = UIView()
let sView = UIView()
pView.backgroundColor = UIColor.red
sView.backgroundColor = UIColor.orange
stackView.addArrangedSubview(pView)
stackView.addArrangedSubview(sView)
stackView.layoutIfNeeded()
print(stackView.arrangedSubviews[0].frame)
print(stackView.arrangedSubviews[1].frame)
}
OR you can wait until viewDidAppear and check there.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print(stackView.arrangedSubviews[0].frame)
print(stackView.arrangedSubviews[1].frame)
}

How to use rbcollectionviewinfofolderlayout with custom UICollectionViewCell & CollectionViewLayout

What I want to do is display a UICollectionView inside a UICollectionViewCell when this cell is selected. I want to use rbcollectionviewinfofolderlayout to fold out my collection view and show a new collection view inside of this. However I'm not sure how I can implement this into my existing code. I have three collection views inside one view. I hide and unhide my views accordingly to what selection the user makes. I use a custom cell xib for the cells inside my collection views and I have a custom collectionviewflowlayout that makes sure there are always 3 cells showing in the width of the device.
This is what my view controller looks like.
in my viewDidLoad I have to set the RBCollectionViewInfoFolderLayout to my collectionview. As you can see the layout variable holds my CustomCollectionViewFlow which I set as the collection view layout before I implemented the RBCollectionviewinfofolderlayout.
override func viewDidLoad() {
super.viewDidLoad()
musicLib.loadLibrary()
PlaylistCollectionView.indicatorStyle = UIScrollViewIndicatorStyle.White
AlbumCollectionView.indicatorStyle = UIScrollViewIndicatorStyle.White
ArtistCollectionView.indicatorStyle = UIScrollViewIndicatorStyle.White
layout = CustomCollectionViewFlow()
cview = ArtistCollectionView
let lay: RBCollectionViewInfoFolderLayout = ArtistCollectionView.collectionViewLayout as! RBCollectionViewInfoFolderLayout
lay.cellSize = CGSizeMake(80, 80)
lay.interItemSpacingY = 10
lay.interItemSpacingX = 0
let nib = UINib(nibName: "CollectionViewCell", bundle: nil)
cview.registerClass(UICollectionReusableView.self, forSupplementaryViewOfKind: RBCollectionViewInfoFolderHeaderKind, withReuseIdentifier: "header")
cview.registerClass(UICollectionReusableView.self, forSupplementaryViewOfKind: RBCollectionViewInfoFolderFooterKind, withReuseIdentifier: "footer")
cview.registerClass(collectionViewFolder.self, forSupplementaryViewOfKind: RBCollectionViewInfoFolderFolderKind, withReuseIdentifier: "folder")
cview.registerClass(RBCollectionViewInfoFolderDimple.self, forSupplementaryViewOfKind: RBCollectionViewInfoFolderDimpleKind, withReuseIdentifier: "dimple")
ArtistCollectionView.collectionViewLayout = lay
ArtistCollectionView.registerNib(nib, forCellWithReuseIdentifier: "item")
ArtistCollectionView.dataSource = self
ArtistCollectionView.delegate = self
PlaylistCollectionView.collectionViewLayout = layout
PlaylistCollectionView.registerNib(nib, forCellWithReuseIdentifier: "item")
PlaylistCollectionView.dataSource = self
AlbumCollectionView.collectionViewLayout = layout
AlbumCollectionView.registerNib(nib, forCellWithReuseIdentifier: "item")
AlbumCollectionView.dataSource = self
}
My CustomCollectionViewFlow looks like this
class CustomCollectionViewFlow: UICollectionViewFlowLayout{
override init(){
super.init()
setupLayout()
}
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
setupLayout()
}
override var itemSize: CGSize {
set {
}
get {
let numberOfColumns: CGFloat = 3
let itemWidth = (CGRectGetWidth(self.collectionView!.frame) - (numberOfColumns - 1)) / numberOfColumns
return CGSizeMake(itemWidth, itemWidth)
}
}
func setupLayout() {
minimumInteritemSpacing = 0
minimumLineSpacing = 0
scrollDirection = .Vertical
}
}
I will hold of on putting all my coding here as it will be become to big of a post and the other methodes are kind of irrelevant at this point. However what I did do is put the example application that I made for this on git here for anyone who wants to check it out.
This image shows what the state of my collectionview was before I implemented the rbcollectionview. The second image shows what I'm trying to achieve
This is how the view should look when an item is being tapped
EDIT
I have been able to get it working kind of. I was able to show the layout like I desired. Just like I had it before I implemented the rbcollectionviewinfofolderlayout. However it seems that when the folder is bigger then the screen size it won't actually fold out. It will fold out for a second and collapse again. It might be caused by the layout i've implemented. Below is the code that is responsible for this.
the class that is responsible for my layout
class RBCollectionLayout: RBCollectionViewInfoFolderLayout
{
var view: UIView!
init(view: UIView){
super.init()
self.view = view
setupLayout()
}
override init(){
super.init()
setupLayout()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupLayout()
}
func setupLayout(){
let numberofItems: CGFloat = 3
let itemWidth = (CGRectGetWidth(view.frame)) / numberofItems
cellSize = CGSizeMake(itemWidth, itemWidth)
interItemSpacingX = 0
interItemSpacingY = 0
}
}
The method that will calculate the desired with when the screen is changed from portrait to landscape
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if(view_artist.hidden == false){
guard let ArtistFlowLayout = ArtistCollectionView.collectionViewLayout as? RBCollectionViewInfoFolderLayout else {
return
}
lay = RBCollectionLayout(view: self.view)
ArtistCollectionView.collectionViewLayout = lay
ArtistFlowLayout.invalidateLayout()
}
}
this is how the layout is being set in my viewdidload
lay = RBCollectionLayout(view: self.view)
ArtistCollectionView.collectionViewLayout = lay
again all my code is available on my git here
Well, I'm probably the only person who can answer your question, since I wrote this control and I doubt anyone else is actually using it.
You seem to be missing a key component which is the delegate method collectionView:viewForSupplementaryElementOfKind:atIndexPath: where you tell the CollectionView what views to use for the various elements. If you go to the GitHub Repo and look at the example you will see that in that method I return 4 different views depending on the viewKind that was asked for. In your code I believe what you want to do is return your custom flow layout based collection view for the RBCollectionViewInfoFolderFolderKind. This would place your 3 cell flow layout view into the expanded folder of the selected cell.
I cloned your repo but it doesn't appear to be up to date with the code you are showing here.

How can I set a UITableView to grouped style

I have a UITableViewController subclass with sections. The sections are showing with the default style (no rounded corners). How can I set the TableView style to grouped in the code? I'm not using Interface Builder for this, so I need something like
[self.tableView setGroupedStyle]
I searched on Stack Overflow, but couldn't come up with an answer.
You can do the following:
UITableView *myTable = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
Swift 3:
let tableView = UITableView.init(frame: CGRect.zero, style: .grouped)
If i understand what you mean, you have to initialize your controller with that style. Something like:
myTVContoller = [[UITableViewController alloc] initWithStyle:UITableViewStyleGrouped];
I give you my solution, I am working in "XIB mode", here the code of a subclass of a UITableViewController :
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithStyle:UITableViewStyleGrouped];
return self;
}
Below code Worked for me, I am also using UITableview class
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:UITableViewStyleGrouped];
if (self)
{
}
return self;
}
If you are inheriting UITableViewController, you can just init tableView again.
Objective C:
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
Swift:
self.tableView = UITableView(frame: CGRect.zero, style: .grouped)
Setting that is not that hard as mentioned in the question. Actually it's pretty simple. Try this on storyboard.
Swift 4+:
let myTableViewController = UITableViewController(style: .grouped)
Swift 4
Using Normal TableView
let tableView = TableView(frame: .zero, style: .grouped)
Using TPKeyboardAvoidingTableView
let tableView = TPKeyboardAvoidingTableView(frame: .zero, style: .grouped)
If you create your UITableView in code, you can do the following:
class SettingsVC: UITableViewController {
init() {
if #available(iOS 13.0, *) {
super.init(style: .insetGrouped)
} else {
super.init(nibName: nil, bundle: nil)
}
}
#available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
For set grouped style in ui itself:-Select the TableView then change the "style"(in attribute inspector)) from plain to Grouped.
You can also do this if you want to use it on a subclass you've already created in a separate swift file (probably not 100% correct but works)
override init(style: UITableViewStyle) {
super.init(style: style)
UITableViewStyle.Grouped
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Now in you appdelegate.swift you can call:
let settingsController = SettingsViewController(style: .Grouped)
You can do this with using storyboard/XIB also
Go To storyboard -> Select your viewController -> Select your table
Select the "Style" property in interface-builder
Select the "Grouped"
Done
If you have one TableView for more tables, and one of this tables is grouped and the another one plain, than you can simulate the plain style with the function from UITableViewDelegate:
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return CGFloat.min
}
swift 4
if you don't want use storyboard, this might be help.
you can add table view and set properties in a closure:
lazy var tableView: UITableView = {
let tableView = UITableView(frame: .zero, style: .grouped)
tableView.backgroundColor = UIColor(named: Palette.secondaryLight.rawValue)
tableView.rowHeight = 68
tableView.separatorStyle = .none
tableView.translatesAutoresizingMaskIntoConstraints = false
return tableView
}()
then add in subview and set constraints.
If you're not using Storyboards and just want a clean, concise way to setup your UITableViewController subclass to use a different tableView style, you can simply override loadView like this:
override func loadView() {
let tableView = UITableView(frame: .zero, style: .grouped)
tableView.delegate = self
tableView.dataSource = self
view = tableView
}
As the documentation says:
loadView creates the view that the controller manages
it's a method you can ovveride when using ViewControllers whose views are not defined via Storyboards or NIBs.
The UITableViewController implementation already overrides this method for you to crate a simple, plain UITableView as the root view for the controller, but nothing stops you from further overriding it to create a table view that better suits your needs.
Just remember:
Your custom implementation of this method should not call super.
You can also try to make the separator line color clear which could give the grouped style effect:
[myTVContoller.tableView setSeparatorColor:[UIColor clearColor]];
You can use:
(instancetype)init {
return [[YourSubclassOfTableView alloc] initWithStyle:UITableViewStyleGrouped];
}
self.tableView.style = UITableViewStyleGrouped
EDIT:
Had assumed this was a read/write property. In that case, you can either follow Dimitris advice and set the style when you instantiate the controller, or (if you're using a XIB), you can set it via IB.

Resources