I have a custom UICollectionView. I am using a UIView instead of a UICollectionViewCell as I'm wanting to use the UIView elsewhere.
The UICollectionView scrolls only horizontally and has 14 cells; each with the UIView as its contents.
Inside this UIView is another UICollectionView which houses a UICollectionViewCell which is just an image of a dice.
So:
Outer UICollectionView with Cell
The cell has a UIView
The UIView has a UICollectionView and UICollectionViewCell
The UICollectionViewCell is just an imageView of a dice.
The problem I'm having is, when the UICollectionView scrolls horizontally, the contents of the cell are changing when they should remain static; sometimes it randomly moves the contents, or completely removes the contents.
I'm not sure what is causing this to happen.
How can I ensure that upon scrolling that the UICollectionView keeps its contents intact?
Outer UICollectionView:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return allLocos?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell:OuterCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "OuterCollectionViewCell", for: indexPath) as! OuterCollectionViewCell
if cell.cardView == nil {
let arr = UINib(nibName: "CardView", bundle: nil).instantiate(withOwner: nil, options: nil)
let view = arr[0] as! CardView
cell.contentView.addSubview(view)
cell.cardView = view
}
if let locos = self.allLocos {
let loco:EYLocomotive = locos[indexPath.row]
print(indexPath.row, loco.name, loco.orders)
cell.engineCardView?.setup(loco:loco)
}
cell.layoutIfNeeded()
return cell
}
I think the issue is being caused by this line:
cell.engineCardView?.setup(loco:loco)
My UIView (The cardView) has this code:
#IBOutlet weak var diceCollectionView: UICollectionView!
var dice:[Int] = [Int]()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setup(loco:EYLocomotive) {
self.diceCollectionView.delegate = self
self.diceCollectionView.dataSource = self
self.diceCollectionView.allowsSelection = false
self.diceCollectionView.allowsMultipleSelection = false
self.diceCollectionView.isUserInteractionEnabled = false
self.diceCollectionView.showsVerticalScrollIndicator = false
self.diceCollectionView.showsHorizontalScrollIndicator = false
self.diceCollectionView.register(UINib(nibName: "EYDiceCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "EYDiceCollectionViewCell")
// add dummy dice for testing purposes only
for var _ in 1...3 {
let die = Die().roll
dice.append(die)
}
}
The diceCollectionView is the holder of the Dice Imagery.
When we get to to the UICollectionViewDelegate; in the same file;
// MARK: Collection View Delegate
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dice.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: EYDiceCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "EYDiceCollectionViewCell", for: indexPath) as! EYDiceCollectionViewCell
let dieValue = self.dice[indexPath.row] as Int
let imageFile:String = "die-face-\(dieValue)"
cell.imageView.image = UIImage(named: imageFile)
cell.layoutIfNeeded()
return cell
}
The UICollectionView works fine, the problem comes when I start scrolling horizontally out of the viewport and then scroll back.
Contents of the cell change.
Contents of the cell sometimes change to a different one to the one I'm expecting
Contents of the cell sometimes disappear upon scrolling
What I'd like to know is how can I ensure the contents do not change?
With thanks
Related
I'm creating UICollectionViewCells programmatically. That cell has a Content View (I'm using it only like a decorative frame). Inside that view I create UILabel, UIImageView and another UILabel. All set ups are done when cell is initialized. All works perfect
At ViewController I want to reach that UI Components at method:
CellForItemAt
What will be the best way to do that? Now im working with viewTags to reach particular component, but maybe there is better way?
I need to reach it to fill it with data from CoreData
BR
iMat
As you have created your UICollectionViewCell's UI programmatically, you can have those UI elements as public variable in your UICollectionViewCell.
And Create a public method to set the data into your CustomCell's UI elements as like below.
class CustomCell:UICollectionViewCell{
var imageView:UIImageView!
var label1:UILabel!
var label2:UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
//Initialize your imageView,label1 and label2
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func set(_ text1:String?, text2:String?, image:UIImage?){
imageView.image = image
label1.text = text1
label2.text = text2
}
}
In your cellForItemAt function pass your data to your custom cell as like below.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YourCustomCellID", for: indexPath) as! CustomCell
cell.set("1", text2: "2", image: yourImage)
return cell
}
Let's say you have your custom UICollectionViewCell subclass
class MyCell: UICollectionViewCell {
var item: Item?
...
func setUI() {
label.text = item.someProperty
...
}
...
}
You can downcast your cell as your subclass when you dequeued it
cell as! MyCell
... then you have access to subclass's methods, variables, etc.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "identifier", for: indexPath) as! MyCell
cell.item = array[indexPath.row]
cell.setUI()
...
return cell
}
I am having issues with my collectionView in Swift 3. I have found some tips on Stackoverflow, tried them out, but alas to no avail. Many mentioned to use the 'prepareReuse' method, but I could not get those to work in my code. After Scrolling down, and back up again, the images have changed. All the images are letters of the alphabet. So A,B,C are the first images to appear at the top of the view. If you scroll down, and back up, some random other letters have taken their place. My entire code is as follows:
import UIKit
class colViewController2: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
var imageData: [String] = [String]()
var imageCounter: Int = 0
var userHasHieroglyph: NSArray = ["","","C","D","E","F","G","H","I","J","K","L","M","N","O","","Q","R","S","T","U","V","W","X","Y","Z"]
override func viewDidLoad() {
super.viewDidLoad()
imageData = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]
for b in imageData {
if userHasHieroglyph.contains(b) {
let newHieroglyph = b.lowercased()
imageData[imageData.index(of: b)!] = "h-"+newHieroglyph
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellid", for: indexPath) as! MyImageCell
cell.backgroundColor = UIColor.white
var currImage:String = ""
currImage = self.imageData[self.imageCounter]
self.imageCounter += 1
if self.imageCounter >= self.imageData.count {
self.imageCounter = 0
}
cell.image.image = UIImage(named: currImage)
return cell
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 26
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 90, height: 90)
}
}
I hope I am missing some small bit of code to solve this problem, but after many hours of searching on Stackoverflow, and the internet, I still can not find a solution to this problem. If anyone has a solution or tip, it would be greatly appreciated!
Greetings
The issue is that you're using imageCounter as the index into your array of images, but incrementing it instead of using the indexPath.item. Remember that the UICollectionView will reuse UICollectionViewCell instances. Basically, it will only create cell instances for those that are on the screen. If a cell scrolls off the screen and a new one takes its place (e.g. if you have "A", "B", and "C" on the screen, and scroll down so you see "B", "C", "D", the UICollectionView will reuse the "A" cell for "D". This is a bit of an oversimplification, but more or less how it works). As such, the cellForItem call will give you the information about which cell is being displayed in the indexPath parameter. In this case, you can get rid of all of the self.imageCounter logic and instead do cell.image.image = UIImage(named: imageData[indexPath.row])
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellid", for: indexPath) as! MyImageCell
cell.backgroundColor = UIColor.white
cell.image.image = UIImage(named: imageData[indexPath.row])
return cell
}
I have a collection view on my HomeController (UICollectionView) Class and my PostCell (UICollectionViewCell) class. HomeController has just 4 different cells. I also have a button inside my PostCell that when touched, handles a handleChangeSize function. When I touch that button, I want that specific cells size to change. Preferably change in height, similar to Instagrams "show more" feature on their cells. Any help will be highly, highly appreciated. Thank you... Here's my code:
class HomeController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor(white: 0.90, alpha: 1.0)
collectionView?.register(PostCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.showsVerticalScrollIndicator = false
collectionView?.alwaysBounceVertical = true
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! PostCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height = (view.frame.width) * 9 / 16
return CGSize(width: view.frame.width, height: height + 50 + 50)
}
}
class PostCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
lazy var myButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Change Size", for: .normal)
button.addTarget(self, action: #selector(handleChangeSize), for: .touchUpInside)
return button
}()
func handleChangeSize() {
}
func setupViews() {
addSubview(myButton)
myButton.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
myButton.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I have done this in tableView. follow the same for collectionView.
var expandedCells = [Int]()
here is the cellForRowAtIndexPath method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RequestTableViewCell") as! RequestTableViewCell
cell.selectionStyle = .none
cell.optionButton.tag = indexPath.row
cell.optionButton?.addTarget(self, action: #selector(RequestsViewController.deleteAction), for: UIControlEvents.touchUpInside)
return cell
}
and the action for option button in tableViewCell is
func deleteAction(_ sender: UIButton){
if let button = sender as? UIButton {
// If the array contains the button that was pressed, then remove that button from the array
if expandedCells.contains(sender.tag) {
expandedCells = expandedCells.filter { $0 != sender.tag }
}
// Otherwise, add the button to the array
else {
expandedCells.removeAll()
expandedCells.append(sender.tag)
}
// Reload the tableView data anytime a button is pressed
self.requestTableView.beginUpdates()
self.requestTableView.endUpdates()
}
}
and the heightForRowAtIndexPath method is
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// Set the row height based on whether or not the Int associated with that row is contained in the expandedCells array
if expandedCells.contains(indexPath.row) {
return 112
} else {
return 67
}
}
The solution to this problem is quite easy. First, you need to specify what height you want to set for the particular cell and perform that change in handleChangeSize() method (this could be done by simple frame.size.height = ...).
After that, you probably need to resize the whole collection view, otherwise, there are going to be some nasty surprises for you. Perform the calculation (to get the new height) after you have resized the cell (you might call a notification or something that triggers whenever a to resize is necessary).
I'm fairly new to programming in general, I'm trying to figure out how to create numerous buttons in a complex layout. My layout is basically a transparent PNG of a body cut into about 24 sections and I want each segment of the body to be a separate button.
I've tried a few layouts in the view controller. Setting up a ton of buttons overlaying an image (couldn't keep the layout straight when launching in the simulator) and I've tried giving the buttons images, I've tried large image sized buttons but I could only use the top most button.
Is there any way to do this, or is it going to need code to be doable?
UICollectionViewController or UICollectionView is a better choice for you. You can set the UICollectionViews background image and regard the cells as your buttons.
Here is a quick example.
import UIKit
class Collection: UICollectionViewController {
private let cellId = "cell"
override init(collectionViewLayout layout: UICollectionViewLayout) {
if let l = layout as? UICollectionViewFlowLayout{
l.minimumInteritemSpacing = 0
l.sectionInset = UIEdgeInsetsZero
l.scrollDirection = .Vertical
l.footerReferenceSize = CGSize.zero
l.headerReferenceSize = CGSize.zero
let screenWidth = UIScreen.mainScreen().bounds.width
let itemWidth = CGFloat(Int(screenWidth/4))
l.itemSize = CGSize(width: itemWidth, height: itemWidth)
}
super.init(collectionViewLayout: layout)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.whiteColor()
collectionView?.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
}
}
extension Collection{
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 4
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellId, forIndexPath: indexPath)
cell.backgroundColor = UIColor.redColor()
return cell
}
}
If you want to add touch action to a cell.
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
}
After going through the solutions here on STO and other blogs, none seem to truly fix the UICollectionView issues others definitely seem to still have to this day!
Avoiding the reload functions initially loads the CollectionVC with no issues and scrolling down to the bottom no issues as well, but the moment I scroll up, random cells go missing. Also, noticed that the delegate method below gets called multiple times as I am scrolling, is this normal?
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
Using any of the reload functions results in random cells .backgroundView disappearing:
self.collectionView?.reloadSections(NSIndexSet.init(index: 0))
self.collectionView?.reloadData()
self.collectionView?.reloadItemsAtIndexPaths(cellIndexPathArray)
self.collectionView?.reloadInputViews()
Subclassing UICollectionViewFlowLayout with different override options seems to have no effect.
class CustomCollectionFlowLayout: UICollectionViewFlowLayout {
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
//self.scrollDirection = UICollectionViewScrollDirection.Vertical
return true
//invalidateLayoutWithContext(invalidationContextForBoundsChange(newBounds))
//return super.shouldInvalidateLayoutForBoundsChange(newBounds)
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
/*
let attributes = super.layoutAttributesForElementsInRect(rect)
print(attributes)
let contentSize = collectionViewContentSize()
return attributes?.filter { $0.frame.maxX <= contentSize.width && $0.frame.maxY < contentSize.height }
*/
let attributes = super.layoutAttributesForElementsInRect(rect)
return attributes
}
}
CollectionVC.swift
private let reuseIdentifier = "cell"
class CollectionVC: UICollectionViewController {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
override func viewDidLoad() {
super.viewDidLoad()
//Register cell class
self.collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
}
////////////////////////////////
//UICollectionViewDataSource
////////////////////////////////
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return appDelegate.chosenImageArray.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// Configure the cell
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)
cell.backgroundColor = UIColor.clearColor()
cell.layer.borderColor = UIColor.whiteColor().CGColor
cell.layer.borderWidth = 1
cell.layer.cornerRadius = 8
let bgView = appDelegate.chosenImageArray.objectAtIndex(indexPath.row) as? UIView
if bgView != nil {
cell.backgroundView = bgView
cell.hidden = false
cell.backgroundView?.hidden = false
}
return cell
}
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return appDelegate.deviceSize
//Tried with different sizes CGSize(100,100) - (200,200) and 0 on insets, no difference
}
////////////////////////////////
//UICollectionViewDelegate
////////////////////////////////
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)
//do various stuff with selected cell and segue to next view, no issues here
}
}
Hope someone can tell me it's just a few settings I need to input, as this issue seems random and maybe just a bug with UICollectionView? Anyway, thank you very much in advance for any help.
Yes this is normal every time you scroll the collection view cell and it becomes visible on screen again the delegates get called.
It is getting re used.
In cellForItemAtIndexPath you have
let bgView = appDelegate.chosenImageArray.objectAtIndex(indexPath.row) as? UIView
this above line.
I guess in appDelegate.chosenImageArray you have all the UIViews.
Possible fix
Do not store the UIView directly on array create a NSObject model class and create required properties in it.
-Try to create a NSObject model class which should have all the relevant properties like UIImage , name etc... as per your requirement.
Store that NSObject class in array. Here NSObject class is your model class which you are going to store in your array.
Now in delegate cellForItemAtIndexPath -
let bgViewModel = appDelegate.yourModelClassArray.objectAtIndex(indexPath.row) as? yourModelClass
cell.backgroundView = bgViewModel.bgView
remove cell.hidden = false & cell.backgroundView?.hidden = false it is not required.