For some strange reason the collectionView isn't called. I put break points at numberOfItemsInSection and cellForItemAtIndexPath but they are never called. Here is my code:
class ChannelViewController: UIViewController, UISearchResultsUpdating, UICollectionViewDataSource, UICollectionViewDelegate {
var channelArray: [ChannelInfo] = []
var filteredSearchResults = [ChannelInfo]()
var resultsSearchController = UISearchController(searchResultsController: nil)
var logosShown = [Bool](count: 50, repeatedValue: false)
var detailUrl: String?
var apiKey = ""
var channel: String!
var channelForShow: String!
var task: NSURLSessionTask?
#IBOutlet var channelCollectionView: UICollectionView!
override func viewDidLoad() {
let baseURL = ""
getJSON(baseURL)
}
//MARK: CollectionView
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if resultsSearchController.active && resultsSearchController.searchBar.text != ""
{
return filteredSearchResults.count
} else {
return channelArray.count
}
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("ChannelCell", forIndexPath: indexPath) as! ChannelCell
let channel: String
if resultsSearchController.active && resultsSearchController.searchBar.text != "" {
channel = self.filteredSearchResults[indexPath.row].logo
} else {
channel = self.channelArray[indexPath.row].logo
}
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
var replacedTitle: String?
if resultsSearchController.active && resultsSearchController.searchBar.text != "" {
channelForShow = filteredSearchResults[indexPath.row].channelName
}
} else {
channelForShow = self.channelArray[indexPath.row].channelName
}
}
self.dismissSearchBar()
performSegueWithIdentifier("channelCollectToShowSegue", sender: self)
}
}
I also have a cell subclassed for the collectionView Cell:
class ChannelCell : UICollectionViewCell {
#IBOutlet var channelImageView: UIImageView!
}
You need to set the datasource of collection view to the controller.
1) In Interface builder, just drag from the collection view to the controller. and choose both the delegate and datasource..
2) Programatically
override func viewDidLoad() {
super.viewDidLoad()
channelCollectionView.delegate = self
channelCollectionView.dataSource = self
let baseURL = ""
getJSON(baseURL)
}
Did you set the collectionView's delegate and dataSource properties? If not, change your viewDidLoad to this, in order to set those two properties programmatically:
override func viewDidLoad() {
channelCollectionView.delegate = self
channelCollectionView.dataSource = self
let baseURL = ""
getJSON(baseURL)
}
If you don't set the delegate and dataSource, the collectionView won't know where to look to call the appropriate methods, such as cellForItemAtIndexPath. Hopefully this fixes your problem.
Related
I am facing a problem in my application. When I run my application in the emulator, I don't get any data and just a white screen.
I decided to use Firestore as a backend. Below I provide the code and hope you can help me.
ViewController
class ViewController: UIViewController {
#IBOutlet weak var cv: UICollectionView!
var channel = [Channel]()
override func viewDidLoad() {
super.viewDidLoad()
self.cv.delegate = self
self.cv.dataSource = self
let db = Firestore.firestore()
db.collection("content").getDocuments() {( quarySnapshot, err) in
if let err = err {
print("error")
} else {
for document in quarySnapshot!.documents {
if let name = document.data()["title"] as? Channel {
self.channel.append(name)
}
}
self.cv.reloadData()
}
}
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return channel.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ContentCell
let indexChannel = channel[indexPath.row]
cell.setup(channel: indexChannel)
return cell
}
}
This is my cell
class ContentCell: UICollectionViewCell {
#IBOutlet weak var channelText: UILabel!
#IBOutlet weak var subtitle: UITextView!
func setup(channel: Channel) {
channelText.text = channel.title
subtitle.text = channel.subtitle
}
}
If you need additional information - write
Your problem is here
if let name = document.data()["title"] as? Channel {
self.channel.append(name)
}
you can't cast a String to a Custom data type (Channel) , so according to your data you can try this
if let title = document.data()["title"] as? String , let subtitle = document.data()["subtitle"] as? String {
let res = Channel(title:title,subtitle:subtitle)
self.channel.append(res)
}
I've been searching for days these answers and some how I found it but I don't know how to configure properly to my project.
I followed this awesome video iOS Dev 11: CollectionViews | Swift 5, XCode 11
This video works perfectly to me, but when we reach sizeForItemAt method he declares a static value. For my project, width could be static and I was able to fix according the result I want, but I am still facing difficult to adapt the height. I want it to be equal the text size of my label text, I want I result like this: final result
So, I know that I need to to this on sizeForItemAt method but I don't know how, here is how my code looks like:
FactsCollectionViewCell:
class FactsCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var lbFactsText: UILabel!
#IBOutlet weak var lbCategories: UILabel!
func setup(with facts: FactsData?, index: Int) {
let result = facts!.result[index]
lbFactsText.text = result.value
print(result.value)
if let category = result.categories.first {
lbCategories.text = category!.uppercased()
lbCategories.sizeToFit()
} else {
lbCategories.text = "UNCATEGORIZED"
lbCategories.sizeToFit()
}
}
#IBAction func btShare(_ sender: UIButton) {
// still working on this button
}
}
And this is my MainViewController:
class MainViewController: UIViewController {
var facts: FactsData?
var factsManager = FactsManager()
var numberOfCV = 0
var resultValue: String = ""
#IBOutlet weak var cvFacts: UICollectionView!
#IBOutlet weak var sbSearchFacts: UISearchBar!
#IBOutlet weak var nbMainScreen: UINavigationBar!
override func viewDidLoad() {
super.viewDidLoad()
cvFacts.dataSource = self
cvFacts.delegate = self
cvFacts.collectionViewLayout = UICollectionViewFlowLayout()
sbSearchFacts.isHidden = true
}
#IBAction func search(_ sender: UIBarButtonItem) {
sbSearchFacts.resignFirstResponder()
sbSearchFacts.isHidden = !sbSearchFacts.isHidden
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueMainToFact" {
let vc = segue.destination as! FactViewController
vc.receivedValue = resultValue
}
}
}
extension MainViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return numberOfCV
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = cvFacts.dequeueReusableCell(withReuseIdentifier: "FactsCollectionViewCell", for: indexPath) as! FactsCollectionViewCell
cell.setup(with: facts, index: indexPath.row)
return cell
}
}
// SearchBar delegate extension for when tap to search button
extension MainViewController: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
sbSearchFacts.isHidden = true
sbSearchFacts.resignFirstResponder()
if let realFact = sbSearchFacts.text {
factsManager.factsRequest(realFact) { (facts) in
self.facts = facts
self.numberOfCV = self.facts!.total
DispatchQueue.main.async {
self.cvFacts.reloadData()
}
} onError: { (error) in
print(error)
}
}
}
}
// Extension to configure cell sizes
extension MainViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = view.frame.size.width
return CGSize(width: width - 40, height: //HERE I NEED TO CALCULATE THE HEIGHT BUT I DONT KNOW HOW)
}
}
// Extension for when you tap the cell
extension MainViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let index = indexPath.row
let result = facts?.result[index]
resultValue = result!.value
}
}
Does anyone has a tip to fix it?
I'm having an issue trying to get my collection view to filter data in the tableview for the category(s) set in the collection view. My goal is to have a category selected in the collection view presents the search results for the selected category in tableview shown from the category shown in the categoryLabel:
the tableview is already connected to the search bar and presents the search results accurately. But I want it to do the same for the selections/category(s) in the collection view to filter out the result selected to present the search results for that specific category in the collection view.
My data is stored in the Cloud Firestore
import Foundation
import UIKit
class Category {
var categoryLabel: String
init(categoryLabel: String) {
self.categoryLabel = categoryLabel
}
class func createCategoryArray() -> [Category] {
var categorys: [Category] = []
let category1 = Category(categoryLabel: "All")
let category2 = Category(categoryLabel: "Flower")
let category3 = Category(categoryLabel: "CBD")
let category4 = Category(categoryLabel: "Pre-Roll")
let category5 = Category(categoryLabel: "Pens")
let category6 = Category(categoryLabel: "Cartridges")
let category7 = Category(categoryLabel: "Concentrate")
let category8 = Category(categoryLabel: "Edible")
let category9 = Category(categoryLabel: "Drinks")
let category10 = Category(categoryLabel: "Tinctures")
let category11 = Category(categoryLabel: "Topical")
let category12 = Category(categoryLabel: "Gear")
categorys.append(category1)
categorys.append(category2)
categorys.append(category3)
categorys.append(category4)
categorys.append(category5)
categorys.append(category6)
categorys.append(category7)
categorys.append(category8)
categorys.append(category9)
categorys.append(category10)
categorys.append(category11)
categorys.append(category12)
return categorys
}
}
import UIKit
class CategoryScrollCell: UICollectionViewCell {
#IBOutlet weak var categoryScroll: UILabel!
#IBOutlet weak var view: UIView!
func setCategory(category: Category) {
categoryScroll.text = category.categoryLabel
}
}
import UIKit
import Firebase
class ProductListController: UIViewController {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var productListCollectionView: UICollectionView!
#IBOutlet weak var productListTableView: UITableView!
var categorys: [Category] = []
var searchActive : Bool = false
var productInventory: [ProductList] = []
var productSetup: [ProductList] = []
override func viewDidLoad() {
super.viewDidLoad()
categorys = Category.createCategoryArray()
productListCollectionView.dataSource = self
productListCollectionView.delegate = self
productListTableView.dataSource = self
productListTableView.delegate = self
searchBar.delegate = self
fetchProducts { (products) in
self.productSetup = products
self.productListTableView.reloadData()
}
}
func fetchProducts(_ completion: #escaping ([ProductList]) -> Void) {
let ref = Firestore.firestore().collection("products")
ref.addSnapshotListener { (snapshot, error) in
guard error == nil, let snapshot = snapshot, !snapshot.isEmpty else {
return
}
completion(snapshot.documents.compactMap( {ProductList(dictionary: $0.data())} ))
}
}
}
extension ProductListController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return productSetup.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ProductListCell") as?
ProductListCell else { return UITableViewCell() }
cell.configure(withProduct: productSetup[indexPath.row])
return cell
}
}
extension ProductListController: UICollectionViewDelegate, UICollectionViewDataSource{
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return categorys.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CategoryScrollCell", for: indexPath) as! CategoryScrollCell
let category = categorys[indexPath.row]
cell.setCategory(category: category)
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print("selected")
self.productSetup.category = self.productInventory[indexPath.row]
}
}
As I understand your question,
First, here is my suggestion:
Don't use the UITableView and UICollectionView. No need to use two UI scroll classes together. You can achieve it using only UICollectionView.
Simply make a collectionView cell for category filter listing and returning it on the 0th index.
Now come to your problem, simply you can use the number of sections in CollectionView to show products with each filtered category.
I'm working on an onboarding flow for my iOS App in Swift. I'd like to allow users to tap other users in a collection view and have it follow those users. I need the collection view to be able to allow multiple cells to be selected, store the cells in an array and run a function once the users taps the next button. Here's my controller code:
class FollowUsers: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
var tableData: [SwiftyJSON.JSON] = []
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var loadingView: UIView!
private var selectedUsers: [SwiftyJSON.JSON] = []
override func viewDidLoad() {
super.viewDidLoad()
self.getCommunities()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getUsers() {
Alamofire.request(.GET, "url", parameters: parameters)
.responseJSON {response in
if let json = response.result.value {
let jsonObj = SwiftyJSON.JSON(json)
if let data = jsonObj.arrayValue as [SwiftyJSON.JSON]? {
self.tableData = data
self.collectionView.reloadData()
self.loadingView.hidden = true
}
}
}
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.tableData.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: UserViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("userCell", forIndexPath: indexPath) as! UserViewCell
let rowData = tableData[indexPath.row]
if let userName = rowData["name"].string {
cell.userName.text = userName
}
if let userAvatar = rowData["background"].string {
let url = NSURL(string: userAvatar)
cell.userAvatar.clipsToBounds = true
cell.userAvatar.contentMode = .ScaleAspectFill
cell.userAvatar.hnk_setImageFromURL(url!)
}
cell.backgroundColor = UIColor.whiteColor()
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell: UserViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("userCell", forIndexPath: indexPath) as! UserViewCell
let rowData = tableData[indexPath.row]
let userName = rowData["name"].string
let userId = rowData["id"].int
selectedUsers.append(rowData[indexPath.row])
print("Cell \(userId) \(userName) selected")
}
}
override func viewDidLoad() {
super.viewDidLoad()
collection.dataSource = self
collection.delegate = self
collection.allowsMultipleSelection = true
self.getCommunities()
}
You should be able to make multiple selections with this.
There are two basic functionalities I want to attach to my collection view:
The cells show expand and collapse behaviour on didSelectItem method.
Cell which gets expanded show a new tableView relevant to the row that was selected.
Presumptions:
Multiple expansions don't take place.
Upon clicking any unexpanded cell,that particular cell should expand and rest all should collapse.
The click of the cells in collection view and internal UITableView inside each cell has to be handled.
Each row of the UICollectionView can attain different height with respect to the size of the UITableView it will load upon click it.
I tried How to expand collectionview cell on didselect to show more info?, but it does not call cellForItemAtIndexPath, and so I am unable to add the new tableView to the cell, or http://www.4byte.cn/question/465532/how-to-expand-collectionview-cell-on-didselect-to-show-more-info.html
while Animate UICollectionViewCell collapsing is still unanswered.
I have a custom cell for this collectionView.
Any help in this regard is appreciated.
Thanks in advance.
Here is a sample, hope it helps to understand, how to implement.
class WSCustomCollectionViewCell: UICollectionViewCell, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var tableView: UITableView!
var tableViewData: Array<String>?
// MARK: UITableviewDataSource
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("wsTableViewCell") as UITableViewCell!
var value = self.tableViewData![indexPath.row] as String
cell.textLabel?.text = value
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.tableViewData != nil {
return self.tableViewData!.count
}
return 0
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
// MARK: Configure Cell
func configureWithDictionary(value: Dictionary<String, AnyObject>) {
var title = value["name"] as? String
if title != nil {
self.titleLabel.text = title
}
var items = value["items"] as? Array<String>
if items != nil {
self.tableViewData = items
self.tableView.reloadData()
}
}
}
--
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var collectionView: UICollectionView!
var collectionViewData: Array<Dictionary<String,AnyObject>>?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var value: Dictionary<String, AnyObject> = ["name":"Some title", "items":["item 1", "item 2"]]
self.collectionViewData = [value]
self.collectionView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: UICollectionViewDataSource
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if self.collectionViewData != nil {
return collectionViewData!.count
}
return 0
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("wsCell", forIndexPath: indexPath) as WSCustomCollectionViewCell
let value = self.collectionViewData![indexPath.row] as Dictionary<String, AnyObject>?
if value != nil {
cell.configureWithDictionary(value!)
}
return cell
}
}
Do not forget set dataSource and delegate in storyboard.
Result:
EDIT
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let value = self.collectionViewData![indexPath.row] as Dictionary<String, AnyObject>?
// WARNING: very dirty solution, only for understanding!
if value != nil {
var isExpand = value!["expand"] as? String
if isExpand != nil && isExpand == "1" {
return CGSizeMake(280, 200)
}
}
return CGSizeMake(280, 44)
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let value = self.collectionViewData![indexPath.row] as Dictionary<String, AnyObject>?
if value != nil {
// WARNING: very dirty solution, only for understanding!
var isExpand = value!["expand"] as? String
if isExpand != nil && isExpand == "0" {
var newValue: Dictionary<String, AnyObject> = ["name":"Some title", "items":["item 1", "item 2"], "expand":"1"]
self.collectionViewData = [newValue]
self.collectionView.reloadData()
}
else {
var newValue: Dictionary<String, AnyObject> = ["name":"Some title", "items":["item 1", "item 2"], "expand":"0"]
self.collectionViewData = [newValue]
self.collectionView.reloadData()
}
}
}
And update configureWithDictionary() method:
func configureWithDictionary(value: Dictionary<String, AnyObject>) {
var title = value["name"] as? String
if title != nil {
self.titleLabel.text = title
}
var items = value["items"] as? Array<String>
var isExpand = value["expand"] as? String
if items != nil && isExpand != nil && isExpand == "1" {
self.tableViewData = items
self.tableView.reloadData()
}
else {
self.tableViewData = nil
self.tableView.reloadData()
}
}