I have a stackView(called 'btnStack') which holds four buttons in a cell(class 'PresentationCell') which I am trying to hide with the following code:
func hideBtnStack() {
let cell = collectionView.visibleCells.first as! PresentationCell
cell.btnStack.isHidden = true
}
However, when I run the code I get the following error:
'Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value'
Is there an easier/better way to access the btnStack in the cell?
The relevant code in cellForRowAt is:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! PresentationCell
}
I also have the btnStack declared in the PresentationCell class as follows:
class PresentationCell: UICollectionViewCell {
#IBOutlet weak var textView: UITextView!
#IBOutlet weak var btnStack: UIStackView!
}
And I first call the hideBtnStack method in the ViewDidLoad as follows:
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
setUpCollectionView()
hideNavBar()
hideBtnStack()
}
Thanks in advance for any help that you can provide.
CjCoax gave me the clue on how to resolve this. It looks like if the hideBtnStack is declared in the viewDidLoad along with the methods for collectionView it executes before the collectionView can be populated. I resolved this by putting a delay around the hideBtnStack method and now it works.
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
setUpCollectionView()
hideNavBar()
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.hideBtnStack()
}
}
I think this solution may help you! try this one.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? PresentationCell else { return UICollectionViewCell() }
if indexPath.item == 0 {
// hide your view
cell.btnStack.isHidden = true
}
}
NOTE: try to avoid from force unwrapping.
I hope this solution works for you.
Related
I am trying to input the arrayOfValues[indexPath.item] as the text for my textLabel in the collection view, but receive an error when I run the program saying 'Fatal error: Index Out of Range'
How would I fix this so that the collectionView cells are populated with information from the arrayOfValues?
Here is the code.
import UIKit
class NetworkViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var firstCollectionView: UICollectionView!
#IBOutlet weak var secondCollectionView: UICollectionView!
let arrayOfOrganizations = ["My Network", "Find Connections", "ss"]
let arrayOfValues = [""]
override func viewDidLoad() {
super.viewDidLoad()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if (collectionView == secondCollectionView) {
return arrayOfOrganizations.count
}
return arrayOfValues.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let firstCell = firstCollectionView.dequeueReusableCell(withReuseIdentifier: "firstCell", for: indexPath) as! FirstCollectionViewCell
firstCell.textLabel.text = arrayOfValues[indexPath.item] //error on this line
if (collectionView == secondCollectionView) {
let secondCell = secondCollectionView.dequeueReusableCell(withReuseIdentifier: "secondCell", for: indexPath) as! SecondCollectionViewCell
secondCell.backgroundColor = .black
return secondCell
}
return firstCell
}
}
class FirstCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var textLabel: UILabel!
}
class SecondCollectionViewCell: UICollectionViewCell {
}
The problem you had was the the first two lines of your cellForItemAt function were getting executed no matter the collectionView. So, essentially, you need to make sure the block of code corresponding to the firstCollectionView gets executed only when collectionView == firstCollectionView, and same goes for the secondCollectionView. In short, you just need to change your function to this instead:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if (collectionView == secondCollectionView) {
let secondCell = secondCollectionView.dequeueReusableCell(withReuseIdentifier: "secondCell", for: indexPath) as! SecondCollectionViewCell
secondCell.backgroundColor = .black
return secondCell
} else {
let firstCell = firstCollectionView.dequeueReusableCell(withReuseIdentifier: "firstCell", for: indexPath) as! FirstCollectionViewCell
firstCell.textLabel.text = arrayOfValues[indexPath.item] //error on this line
return firstCell
}
}
i'm trying to update just one single object inside my costumViewCell,
i've tried collectionView.reloadItems(at: [IndexPath]), but this method updates my entire cell, which results to a very jittering animations.
here is a sample code of my collectionView cell,
class MyCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var buttonA: UIButton!
#IBOutlet weak var buttonB: UIButton!
var myButtonTitle: String? {
didSet{
if let title = myButtonTitle {
self.buttonA.setTitle(title, for: .normal)
}
}
}
var buttonActionCallBack: (()->()?)
override func awakeFromNib() {
super.awakeFromNib()
self.animation()
buttonA.addTarget(self, action: #selector(buttonACallBack), for: .touchUpInside)
}
#objc fileprivate func buttonACallBack() {
self.buttonActionCallBack?()
}
fileprivate func animation() {
UIView.animate(withDuration: 1.0) {
self.buttonA.transform = CGAffineTransform(translationX: 20, y: 20)
self.buttonB.transform = CGAffineTransform(translationX: 20, y: 20)
}
}
}
here is my DataSource method.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! MyCollectionViewCell
let item = mainList[indexPath.row]
collectionView.reloadItems(at: <#T##[IndexPath]#>)
cell.buttonActionCallBack = {
() in
//Do Stuff and Update Just ButtonA Title
}
return cell
}
cheers.
The jittering animation occurs because of this collectionView.reloadItems(at: [IndexPath]) line written inside cellForItemAt which is really wrong approach because cellForItemAt called many a times leads to infinite loop of reloading IndexPath's. Instead of that, you just reload only that part which is necessary when action occurs.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! MyCollectionViewCell
let item = mainList[indexPath.row]
//collectionView.reloadItems(at: <#T##[IndexPath]#>) #removed
cell.buttonActionCallBack = {
() in
//Do Stuff and Update Just ButtonA Title
collectionView.reloadItems(at: [indexPath]) //Update after the change occurs to see the new UI updates
}
return cell
}
[SOLVED]
Solution
When created a Xib File , I hadn't deleted the start UIView. Whereas I had to delete this view and after add new CollectionViewCell in this xib.
Reference: IBAction inside UITableViewCell not called in iOS 9
I use this structure so many times.When I write this delegate with using StoryBoard , it works properly but now it's not. Where is my mistake when use the xib files?
print(indexpath) doesn't work!
import UIKit
class SearchVC: UIViewController {
var searchUid:String?
var comingPage:String?
var searchElements = [ProductElement]()
var collection:UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
if comingPage == "ProductVC" {
print(searchUid!)
}
let searchView : SearchListView = UIView.fromNib()
searchView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(searchView)
searchView.topAnchor.constraint(equalTo: view.safeTopAnchor, constant: 0).isActive = true
searchView.bottomAnchor.constraint(equalTo: view.safeBottomAnchor, constant: 0).isActive = true
searchView.rightAnchor.constraint(equalTo: view.safeRightAnchor, constant: 0).isActive = true
searchView.leftAnchor.constraint(equalTo: view.safeLeftAnchor, constant: 0).isActive = true
searchView.backgroundColor = UIColor.white
collection = searchView.collectionView
collection.translatesAutoresizingMaskIntoConstraints = false
collection.delegate = self
collection.dataSource = self
collection.register(UINib(nibName: "SearchCollectionCell", bundle: nil), forCellWithReuseIdentifier: "SearchCollectionCell")
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 10
collection.collectionViewLayout = layout
GetElements().search(keywords: ["\(searchUid!)"], contentTypes: ["contenttype_article"]) { (elements) in
self.searchElements = elements
self.collection.reloadData()
}
}
}
extension SearchVC: UICollectionViewDelegate, UICollectionViewDataSource , UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return searchElements.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var cell: SearchCollectionCell! = collectionView.dequeueReusableCell(withReuseIdentifier: "SearchCollectionCell", for: indexPath) as? SearchCollectionCell
if cell == nil {
collectionView.register(UINib(nibName: "SearchCollectionCell", bundle: nil), forCellWithReuseIdentifier: "SearchCollectionCell")
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SearchCollectionCell", for: indexPath) as? SearchCollectionCell
}
let url = URL(string: "\(String(describing: Config.fileServiceWFileUid!))\(String(describing: searchElements[indexPath.row].oneImage!))")
cell.searchImage.kf.setImage(with: url)
cell.productName.text = searchElements[indexPath.row].title
cell.productCompany.text = searchElements[indexPath.row].description
cell.delegate = self
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.bounds.size.width / 2 - 5 , height: 175)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// print(indexPath.row)
}
}
extension SearchVC : SearchCollectionCellDelegate {
func searchCellShareButton(sender: SearchCollectionCell) {
print("AA")
if let indexpath = collection.indexPath(for: sender) {
print(indexpath)
}
}
}
//
protocol SearchCollectionCellDelegate{
func searchCellShareButton(sender:SearchCollectionCell)
}
class SearchCollectionCell: UICollectionViewCell {
#IBOutlet var searchImage: UIImageView!
#IBOutlet var productName: UILabel!
#IBOutlet var productCompany: UILabel!
var delegate:SearchCollectionCellDelegate?
override func layoutSubviews() {
searchImage.layer.cornerRadius = 4
}
#IBAction func cellShareButtonAction(_ sender: Any) {
if delegate != nil {
delegate?.searchCellShareButton(sender: self)
}
}
}
[EDIT]
I added didSelectItemAt func. When I try to press "..." button for calling protocol, didSelectItemAt works. I think also this is another mistake.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath.row)
}
[EDIT 2]
AddTarget Action didn't work. Where is my mistake? Please help me!!!
#IBOutlet var shareButton: UIButton!
weak var delegate:SearchCollectionCellDelegate?
override func awakeFromNib() {
shareButton.addTarget(self, action: #selector(asd), for: .touchUpInside)
}
#objc func asd(){
print("asd")
}
Used the same code of your's and it is working perfectly fine. Can't figure out why it is not working at your end.
If you are not getting the solution try Closures :
class SecondCollectionViewCell: UICollectionViewCell {
var callbackOnButton : (()->())?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
#IBAction func methodButton(_ sender: Any) {
self.callbackOnButton?()
}
}
and in cellForRowAtIndex add :
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SecondCollectionViewCell", for: indexPath) as! SecondCollectionViewCell
cell.callbackOnButton = {
print("Button Clicked")
}
return cell
}
try this
in viewDidLoad
override func viewDidLoad() {
collectionView.register(UINib(nibName: "SearchCollectionCell", bundle: nil), forCellWithReuseIdentifier: "SearchCollectionCell")
}
in your cell class
protocol SearchCollectionCellDelegate:class{
func searchCellShareButton(sender:SearchCollectionCell)
}
class SearchCollectionCell: UICollectionViewCell {
#IBOutlet var searchImage: UIImageView!
#IBOutlet var productName: UILabel!
#IBOutlet var productCompany: UILabel!
weak var delegate:SearchCollectionCellDelegate?
override func layoutSubviews() {
searchImage.layer.cornerRadius = 4
}
#IBAction func cellShareButtonAction(_ sender: Any) {
delegate?.searchCellShareButton(sender: self)
}
}
now go to your controller conform protocol
class yourViewController:UIVIewController, SearchCollectionCellDelegate{
}
in you data source method
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell: SearchCollectionCell! =
collectionView.dequeueReusableCell(withReuseIdentifier:
"SearchCollectionCell",
for: indexPath) as? SearchCollectionCell else{return UICollectionViewCell() }
cell.delegate = self
return cell
}
Have you registered CollectionViewCell Class with proper identifier in your view controller...
if not try to register it in viewDidLoad Method.
What is the correct way to pass data from my ViewController to my CollectionViewItem?
ViewContoller
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let item = CollectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "CollectionViewItem"), for: indexPath) as! CollectionViewItem
item.themeName = getDirectoryContentByIndex(index: indexPath)
return item
}
CollectionViewItem
class CollectionViewItem: NSCollectionViewItem {
var themeName: String!
#IBOutlet weak var NameOfTheme: NSTextFieldCell!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
}
Edit:
I want to assign the themeNameas stringValue of NameOfTheme. But if I set the value, the IBOutlet is nil.
I recommend you to make fields of your cell private and make configure(YOUR_PARAMS) (in our case configure(themeName: String). So in cellForRow you will call this function cell.configure(themeName: "")
In my iOS app, I have a UICollectionView controller with the following code inside:
var images = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
loadImages()
// Register cell classes
}
func loadImages() {
images.append(UIImage(named: "ActivityTab")!)
self.collectionView?.reloadData()
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return images.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! EventCollectionViewCell
let image = images[indexPath.row]
cell.imageView.image = image
return cell
}
And I have a UICollectionViewCell (EventCollectionViewCell) with the following code:
#IBOutlet weak var imageView: UIImageView!
override func prepareForReuse() {
super.prepareForReuse()
self.imageView.image = nil
}
I have linked everything up with the storyboard too. This looks like it should work but I get the following error:
Could not cast value of type 'UICollectionViewCell' (0x1092c04a8) to EventCollectionViewCell'
Does anyone know what the error is and how to fix it. It only shows when I return images.count in the number of sections.
I am using xcode 9 but have tried with xcode 8 and the error still persists. Any help would be greatly appreciated. :)
Let change this line of code:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! EventCollectionViewCell
to:
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as? EventCollectionViewCell else { return UICollectionViewCell() }
Check for the following things:
EventCollectionViewCell is a subclass of UICollectionViewCell.
Have you registered EventCollectionViewCell with the collectionView?
EventCollectionViewCell has the same Reusable Identifier that you are using while calling dequeueReusableCell with "reuseIdentifier".
What is the value of reuseIdentifier here?