0

I followed the attached guide to creating static UICollectionView but now I would like to add buttons to each cell and change the text on the buttons, for example. I can not do this and get the error "UIButton is invalid. Outlets cannot be connected to repeating content." How can I fix this issue and use IBOutlets with objects in cells without leaving the ViewController?

If I need to leave ViewController please describe the process with a lot of detail as I am a beginner and am not too knowledgeable on the different view classes.

Thank you!!

Tyler Dakin
  • 65
  • 1
  • 10

2 Answers2

4

Instead of the outlet between the button and the view controller, you should create a subclass of UICollectionViewCell, and add your IBOutlets on that class.

class MyCollectionViewCell: UICollectionViewCell {
    @IBOutlet var myButton: UIButton!
}

Then, in Interface Builder, set this subclass to be the class of your cells (in the Identity inspector pane).

Identity inspector

You should then be able to create the outlet connection from your button to your cell.

Adding outlet connection

I hope this is clear enough. If not, please let me know!

Example code

class MyCollectionViewCell: UICollectionViewCell {
    @IBOutlet var myButton: UIButton!
}

class MyViewController: UIViewController, UICollectionViewDataSource {
    @IBOutlet var myCollectionView: UICollectionView!

    private var isMyButtonEnabled = true

    // Other view controller code

    func disableMyButton() {
        self.isMyButtonEnabled = false
        self.myCollectionView.reloadData()
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = ... as! MyCollectionViewCell // Get cell
        // Other cell setup

        cell.myButton.isEnabled = self.isMyButtonEnabled

        return cell
    }
}
Gori
  • 347
  • 2
  • 13
  • Yes!! Thank you so much! I think I've finally conquered UICollectionView... by... copying people on the internet ¯\_(ツ)_/¯ At least I'm learning haha. Thanks again! – Tyler Dakin Oct 06 '18 at 00:42
  • I've done this and it has worked in my project but now I need to adjust properties of the buttons in the cell using a function that's executed in the main ViewController class. How would I go about doing that? – Tyler Dakin Oct 08 '18 at 14:57
  • @TylerDakin Have a look at [this tutorial](https://robkerr.com/how-to-create-a-static-uicollectionview-af8ce3bf07ac). You could add a function to your subclass and then call that function in the `cellForItem` datasource function – Gori Oct 08 '18 at 15:09
  • I did use this to set up the UICollectionView, I don't remember it going over that but I'll take a second look, thanks. – Tyler Dakin Oct 08 '18 at 15:11
  • To clarify, in my program when a stepper value is changed, the function is run to check whether or not the button needs to be disabled and, if it does, disables the button. – Tyler Dakin Oct 08 '18 at 15:18
  • @TylerDakin In that case, this solution isn't suitable. Maybe you can try to add a **weak** property for each button to your view controller, and set these in the datasource function. Then you can access them from the view controller itself – Gori Oct 08 '18 at 15:36
  • I'm not sure I know what you mean. Do you know of any examples I could look at? – Tyler Dakin Oct 08 '18 at 15:44
  • Sorry forgot to ping you in the last comment. Are there any examples you know of that I'd be able to look at to further understand your suggestion? – Tyler Dakin Oct 09 '18 at 02:04
  • @TylerDakin I can't think of any specific examples, but now that I think of it again, you could do the 'disable check' in the `cellForItem` function of the datasource, and then just reload the collection view when the stepper changes. Hope this is clear :) – Gori Oct 09 '18 at 07:44
  • I can't seem to find anything that explains 'disable check'. Would you be able to explain that or write some example code, please? Also, would you like to move this discussion to another platform that will allow us to DM? – Tyler Dakin Oct 09 '18 at 12:15
  • @TylerDakin Ah what I mean with that is that you set the buttons to be enabled/disabled based on some logic (the stepper), maybe I didn't choose the best description for it. I cannot really write example code in a comment, unfortunately. What platform would you suggest? – Gori Oct 09 '18 at 12:20
  • Discord would work best for me but Slack works well too if you use that. – Tyler Dakin Oct 09 '18 at 12:21
  • @TylerDakin I use neither of those platforms, but I've updated my answer to include more sample code to clarify. Basically, you would use the `disableMyButton` function, to update the property and reload the collection view. This causes the `cellForItem` function to be called again, thus disabling `myButton`. Does this clarify things? – Gori Oct 09 '18 at 12:38
  • Sorry for dragging this out so much but I really appreciate the help. The only thing I'm confused about here is the "..." in the cellForItem func. Also, I have a similar function with other instructions as an extension. Should your code replace that or go in that function as well? – Tyler Dakin Oct 09 '18 at 15:20
  • @TylerDakin No problem, happy to help! The ... should be replaced with something like `myCollectionView.dequeueReusableCell` ([docs here](https://developer.apple.com/documentation/uikit/uicollectionview/1618063-dequeuereusablecell)). Have a look at the tutorial in [this comment](https://stackoverflow.com/questions/52673403/uicollectionview-cant-use-iboutlets-in-viewcontroller-swift/52673698?noredirect=1#comment92336913_52673698) I posted earlier, it explains it a bit. – Gori Oct 09 '18 at 15:45
  • Any idea why myButton would be returning nil? It still has the connection to my storyboard. – Tyler Dakin Oct 10 '18 at 16:35
  • @TylerDakin No idea, sorry. Are you sure it's connected to your storyboard? And at what point is it nil? – Gori Oct 10 '18 at 16:59
  • Definitely connected. Returns nil at the line that it is referenced. Xcode suggests that I use ?? To specify what to do when nil is found but i don't know why nil is being found at all. – Tyler Dakin Oct 10 '18 at 19:11
  • @TylerDakin Maybe we can discuss further via something like ChatCrypt? – Gori Oct 10 '18 at 20:05
  • Okay sure. If you could set up a group and give me the details I'll get signed in. – Tyler Dakin Oct 12 '18 at 01:10
1

Define class like following for your collection view:

class MyCollectionCell : UICollectionViewCell {
    @IBOutlet weak var likeButton: UIButton?
}

Create xib for collection cell and use above custom class for collection view.

Now in your view controller define collection view and implement following delegates UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout.

class ViewController: UIViewController, UICollectionViewDataSource, 
         UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {

    @IBOutlet var collectionView: UICollectionView!

    override func viewDidLoad() {
        super.viewDidLoad()
        let nib = UINib(nibName: "MyCollectionViewCell", bundle: nil)
        collectionView?.registerNib(nib, forCellWithReuseIdentifier: "myCell")
    }

    //UICollectionViewDelegateFlowLayout methods
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat
    {
        return 4;
    }

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat
    {
        return 1;
    }   

    //UICollectionViewDatasource methods
    func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int 
    {
        return 1
    }

    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 100
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        var cell = 
        collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as MyCollectionCell
        cell.likeButton.setTitle("myTitle", for: .normal)
        cell.likeButton.tag = indexPath.row
        cell.likeButton.addTarget(self, action: #selector(mainButton:), forControlEvents: .TouchUpInside)
        return cell
    }

    @IBAction func mainButton(sender: UIButton) {
      println(sender)
      // use button tag to find out which button is clicked.
    }
}

In above code important method is func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell where you set tag to your button and then use that tag to find out which button is pressed and use that id to find out data source or action you want to perform.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Rohit
  • 181
  • 1
  • 10