How to copy/duplicate a custom collectionview cell in Swift? - ios

I want to copy a custom collectionview cell from a viewcontroller to another, the problem is the collectionview cell disappears once I tap on it because of - apparently - adding it in the 2nd viewcontroller's as a subview.
I tried most of the methods here Create a copy of a UIView in Swift
One about Archiving returns nil.
Other about prototypes and structs, it doesn't return a new address for the view.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
struct ObjectToCopied {
var objToBeRetrieved: UIView? = nil
init(objToSaved : UIView?) {
objToBeRetrieved = objToSaved
}
}
let pickedView : UIView? = collectionView.cellForItem(at: indexPath)
let tempObj = ObjectToCopied(objToSaved: pickedView)
let copiedObj = tempObj
let copiedView = copiedObj.objToBeRetrieved as? UIView
// here the copiedview's address is THE SAME as tempObj's ObjToBeRetrieved.
}
Custom CollectionView cell image example:

Don't copy UIView object!
Your model isn't good.
You have to store the data – information somewhere outside the cell directly.
You have to call your 'database' using indexPath to retrieve the information. No more!
About your question.
This object doesn't copy your object.
struct ObjectToCopied {
var objToBeRetrieved: UIView? = nil
init(objToSaved : UIView?) {
objToBeRetrieved = objToSaved
}
}
ObjectToCopied is a value type. But UIView is a reference type. So, objToBeRetrieved contains the same address as pickedView. Solution? Use objToBeRetrieved = objToSaved.copy(). But, please, really don't do it. That's really bad. Futhermore, I think this will corrupt something. Use better application model – the first suggestion.

Why do you need to copy the collectionView cell? You can simply pass your data to next viewController and create new view or cell (whatever you need) and display data on it.
Any specific reason to copy collectionView cell?

Related

Transferring Data from one View to another in Swift 3

Following Situation:
I have 2 Controllers, a ViewController and a CollectionViewController. The normal ViewController is supposed to collect data from the user and when the start button is clicked, an algorithm solves the problem and returns the result. The Result is supposed to be transferred to the CollectionViewController, and based on the solution the CollectionView will be built.
Below is the code I used for the start button. As you can see, the Algorithm is called, the results are stored in several variables and now I was trying to transfer the matrixArray to my CollectionViewController (its a first test). The CollectionViewController should use the data stored in this array to present some form of tableau
#IBAction func startButton(_ sender: Any) {
...
let solution = PrimalSimplex(problem: problem, currentSolution: currentSolution)
matrixArray = solution.0
basicArray = solution.1
maxArray = solution.2
currentSolutionArray = solution.3
isOptimal = solution.4
isCyceling = solution.5
let CollectionVC = storyboard?.instantiateViewController(withIdentifier: "CollectionView") as! CollectionViewController
CollectionVC.testMatrix = matrixArray
}
So far so good, the data arrives is available in the CollectionViewController after the start button is pushed. But when I try to use the data to build the CollectionView, I get an error message.
This is the code that I used in the collectionViewController to build the CollectionView (It worked before, with static values... the problems occur when I try to use values that the algorithm returns):
class CollectionViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var myCollectionView: UICollectionView!
// Creates an empty array for the values
var testMatrix = Array<Matrix>()
//Setup CollectionView: Table to display LPs
let reuseIdentifier = "cell"
var items = testMatrix[0] <----ERROR
// MARK: - UICollectionViewDataSource protocol
// tell the collection view how many cells to make
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
// make a cell for each cell index path
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! MyCollectionViewCell
// Use the outlet in our custom class to get a reference to the UILabel in the cell
cell.myLabel.text = items[indexPath.item]
cell.backgroundColor = UIColor(red:0.94, green:0.94, blue:0.94, alpha:1.0) // make cell more visible in our example project
// Change shape of cells
cell.layer.cornerRadius = 8
return cell
}
....
The error is displayed at var items = testMatrix[0]:
Cannot use instance member 'testMatrix' within property initializer; property initializers run before 'self' is available
I can understand that Xcode has a problem here, because it can't be sure that the testMatrix has values stored.... I think that the problem. I tried to use if let/ guard statement, but that didnt solve the problem.
Any advice on how to fix it or whats actually wrong here?
Maybe there is a better way to transfer the data from the first VC to the other one?
You can't initialize property from other dependent property at the class level.
You should try to initialized in viewDidLoad.
var items: Matrix?
override func viewDidLoad() {
super.viewDidLoad()
if testMatrix.count>0{
items = testMatrix[0]
}
}

Add value to label inside nested collection view in Swift 3

I need to be able to set the text label inside a nested collection view.
I can show each of the values inside a print statement but when I assign it to the UILabel I get an fatal error: unexpectedly found nil while unwrapping an Optional value
I'm loading in the data from the parent collection view and assigning it to the data source:
private var timeZone: [AlternativeTimeZone]!
var setTimeZones: [AlternativeTimeZone] {
get {
return self.timeZone
}
set {
self.timeZone = newValue
}
}
childcollectionviewService.setTimeZones = alternateTimesZonesData
The service is an external class which houses all reference to the parent collection view in order to keep the view controller small.
My nested collection view code looks standard.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AlternateTimeZoneCell", for: indexPath) as? AlternateTimeZoneCell else { return UICollectionViewCell() }
print("Time Zone:", timeZone[indexPath.row].time)
cell.alternateTime.text = timeZone[indexPath.row].time
return cell
}
AlternativeTimeZone is a simple struct with a text string property. I am inside a nested collection view so could that indexPath be overriding the childs?
It sounds to me like you have a broken outlet link in the cell template for the field alternateTime in your AlternateTimeZoneCell. Check to see if that's nil in the cell that you load. Also check to see if the dequeue is succeeding.
I managed to solve this by removing the data source and delegate declaration into the awakeFromNib method as it turns out the init was trying to set the alternateTime value before the data was given to it.

How to set the title of a UIButton in a UICollectionViewCell with Swift

I have a ViewController with a UICollectionView where I'd like to display the first two letters of the players in the users friend list, like this:
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
contactListCollection.registerClass(PlayerCell.self, forCellWithReuseIdentifier: "PlayerCell")
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("PlayerCell", forIndexPath: indexPath) as! PlayerCell
let contacts = contactList.getContactList() //Array of strings
for(var i = 0; i < contacts.count; i++){
var str = contacts[i]
// First two letters
let firstTwo = Range(start: str.startIndex,
end: str.startIndex.advancedBy(2))
str = str.substringWithRange(firstTwo)
cell.setButtonTitle(str);
}
return cell;
}
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return contactList.getContactList().count;
}
My PlayerCell Class is as follows:
class PlayerCell : UICollectionViewCell {
#IBOutlet var button: UIButton?
func setButtonTitle(text: String){
button!.setTitle(text, forState: .Normal)
}
}
When run the code it gives:
Fatal error: unexpectedly found nil while unwrapping an Optional value
I found out that the button in my PlayerCell is nil
I have added the button inside my cell in the Storyboard and Connected those with referencing outlets
Am I missing something here?
Using xCode 7 with Swift 2.0
As part of the compilation process, Xcode converts the storyboard to a collection of XIB files. One of those XIB files contains your cell design.
When your app loads the collection view controller that you designed in the storyboard, the controller takes care of registering the cell's XIB file for the cell.
You are overwriting that registration by calling registerClass(_:forCellWithReuseIdentifier:), severing the connection between the “PlayerCell” reuse identifier and the XIB containing the design of PlayerCell.
Get rid of this line:
contactListCollection.registerClass(PlayerCell.self, forCellWithReuseIdentifier: "PlayerCell")
Also, make sure that in the storyboard you have set the cell's reuse identifier to "PlayerCell".
Try:
1. I would check that there isn't an old referencing outlet attached to the button. Right click on the button in the interface builder and ensure that only the appropriate outlet is still connected.
Ensure that in the storyboard you have set the class of the re-useable cell to PlayerCell
Ensure that you have Ctrl + dragged from the collection view to the view controller it is in and set the delegate and data source to itself.
Hopefully, one of these may help you.

How to set datasource and delegate Outside of View Controller

This might sound like an odd question but I'm trying to implement the BEMSimpleLineGraph library to generate some graphs that I have place in a UITableView. My question is how I reference an external dataSource and Delegate to have different graphs placed in each cell (BEMSimpleLineGraph is modelled after UITableView and UICollectionView). I currently have something like this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: FlightsDetailCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as FlightsDetailCell
cell.userInteractionEnabled = false
if indexPath.section == 0 {
cell.graphView.delegate = GroundspeedData()
cell.graphView.dataSource = GroundspeedData()
return cell
}
if indexPath.section == 1 {
cell.graphView.delegate = self
cell.graphView.dataSource = self
return cell
}
return cell
}
My dataSource and Delegate for section 1 is setup properly below this and the GroundspeedData class looks like this:
class GroundspeedData: UIViewController, BEMSimpleLineGraphDelegate, BEMSimpleLineGraphDataSource {
func lineGraph(graph: BEMSimpleLineGraphView!, valueForPointAtIndex index: Int) -> CGFloat {
let data = [1.0,2.0,3.0,2.0,0.0]
return CGFloat(data[index])
}
func numberOfPointsInLineGraph(graph: BEMSimpleLineGraphView!) -> Int {
return 5
}
}
For some reason when I run the app, Xcode reports that it cannot find the dataSource for section 0, specifically "Data source contains no data.". How should I otherwise reference this alternate dataSource?
cell.graphView.delegate = GroundspeedData()
cell.graphView.dataSource = GroundspeedData()
One problem is: the delegate and data source are weak references. That means they do not retain what they are set to. Thus, each of those lines creates a GroundspeedData object which instantly vanishes in a puff of smoke. What you need to do is make a GroundspeedData object and retain it, and then point the graph view's delegate and data source to it.
Another problem is: do you intend to create a new GroundspeedData object or use one that exists already elsewhere in your view controller hierarchy? Because GroundspeedData() creates a new one - with no view and no data. You probably mean to use a reference to the existing one.

viewWithTag in UICollectionViewCell returns nil in Swift (until cell is reused)

This lengthy title is roughly my problem. I started simple learning example using UICollectionView in Swift project.
I added CollectionView to ViewController created by template, assigned delegate and data source. Custom cell is created in storyboard. Reuse identifier is set.
Everything is fine so far. I have placed one UILabel in custom cell, and gave tag value 100.
Here's code of my ViewController: https://gist.github.com/tomekc/becfdf6601ba64d5fd5d
And interesting exceprt below:
func collectionView(collectionView: UICollectionView!, cellForItemAtIndexPath indexPath: NSIndexPath!) -> UICollectionViewCell! {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("element", forIndexPath: indexPath) as UICollectionViewCell
cell.backgroundColor = UIColor.yellowColor()
// list subviews
NSLog("--- ROW %d ---", indexPath.row)
printSubviews(cell)
if let labelka = cell.viewWithTag(100) as? UILabel {
labelka.text = String(format: "Row %d", indexPath.row)
NSLog("Found label")
}
return cell
}
func printSubviews(view:UIView) {
if let list = view.subviews as? [UIView] {
for uiv in list {
NSLog("%# tag:%d", uiv, uiv.tag)
printSubviews(uiv)
}
}
}
The problem is that cell.viewWithTag(100) returns nil until cell is reused. When I scroll the view so any of cells goes out of window and reuse is forced, viewWithTag(100) returns the label and I can set its value.
What's interesting, I put together similar example in Objective-C and there is no such problem. Even when built and run with XCode6 beta4.
I wonder if I missed something or this is wrong behavior?
Update: apparently I took too simplistic approach. When I created custom UICollectionViewCell subclass (as I usually do), result is correct.

Resources