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]
}
}
Related
Using protocol / delegate & retrieve the data from didSelectItemAt (collectionViews).
// This class have the data
// WhereDataIs: UICollectionViewController
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//Pass some data from the didSelect to a delegate
let test = things[indexPath.item]
guard let bingo = test.aThing else { return }
print("bingo: ", bingo)
That bingo is printing the value that I need. So pretty good right there.
Now, I can't use the method of the protocol inside of that function, that's bad execution so the compiler or Xcode says hey, you will declare the method as a regular method, not the nested way.
//Bridge
protocol xDelegate {
func x(for headerCell: HeaderCell)
}
//This is the class that need the data
//class HeaderCell: UICollectionViewCell
var xDelegate: xDelegate?
//it's init()
override init(frame: CGRect) {
super.init(frame: frame)
let sally = WhereDataIs()
self.xDelegate = sally
xDelegate?.x(for: self)
}
// This extension is added at the end of the class WhereDataIs()
// Inside of this class is it's delegate.
var xDelegate: xDelegate? = nil
extension WhereDataIs: xDelegate {
func x(for headerCell: HeaderCell) {
//Smith value will work because it's dummy
headerCell.lbl.text = "Smith"
// What I really need is instead of "Smith" is the value of didselectItemAt called bingo.
headerCell.lbl.text = bingo
}
}
Hat's off for anyone who would like to guide me in this.
Not using delegates, but it will work
Solved by:
1) go to the controller of the collectionView. make a new variable to store the items.
// Pass the item object into the xController so that it can be accessed later.
//(didSelectItemAt)
xController.newVar = item
//xController class: UICollectionView...
// MARK: - Properties
var newVar: Thing?
Finally in that class, you should have the the method viewForSupplementaryElementOfKind in which you register the HeaderCell and then just add...
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerId, for: indexPath) as! HeaderCell
header.lbl.text = newVar.whatEverYourDataIs
Little generic but it works. :)
I think you can just add this to the end of your current code in didSelectItemAt indexPath:
guard let header = collectionView.supplementaryView(forElementKind: "yourElementKind", at: indexPath) as? HeaderCell else { return }
header.lbl.text = bingo
collectionView.reloadData()
Edit: Keep everything else for now to ensure you get a good result first.
Let me know if this works, happy to check back.
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?
I have a shop menu for my game in swift. I am using a UICollectionView to hold the views for the items. There is a black glass covering over them before they are bought, and it turns clear when they buy it. I am storing data for owning the certain items in the cells' classes. When I scroll down in the scrollView and then come back up after clicking a cell. A different cell than collected has the clear class and the one I previously selected is black again.
import UIKit
class Shop1CollectionViewCell: UICollectionViewCell {
var owned = Bool(false)
var price = Int()
var texture = String()
#IBOutlet weak var glass: UIImageView!
#IBOutlet weak var ball: UIImageView!
func initiate(texture: String, price: Int){//called to set up the cell
ball.image = UIImage(named: texture)
if owned{//change the glass color if it is owned or not
glass.image = UIImage(named: "glass")
}else{
glass.image = UIImage(named: "test")
}
}
func clickedOn(){
owned = true//when selected, change the glass color
glass.image = UIImage(named: "glass")
}
}
Then I have the UICollectionView class
import UIKit
class ShopViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
struct ball{
var price = Int()
var texture = String()
var owned = Bool()
}
var balls = Array<ball>()//This is assigned values, just taken off of the code because it is really long
override func viewDidLoad() {
super.viewDidLoad()
balls = makeBalls(
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return (balls.count - 1)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Shop1CollectionViewCell
cell.initiate(texture: balls[indexPath.item].texture, price: 1)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! Shop1CollectionViewCell
cell.clickedOn()
}
}
I am wondering why the variables stored in one class of a cell are being switched to another.
Please ask if you need me to add any additional information or code.
You should definitely implement prepareForReuse, and set the cell back to its default state - which in this case would be with the black glass, and unowned.
You can still change the glass in the same way as you're doing in didSelectItemAt, but I'd recommend having the view controller track the state of each ball.
For example, when didSelectItemAt gets called - the view controller would update the ball stored in self.balls[indexPath.row] to have owned = true.
That way, the next time cellForItemAt gets called, you'll know what colour the glass should be by checking the value in self.balls[indexPath.row].owned.
Lastly, you're returning balls.count - 1 in numberOfItems, is this intentional?
In the case where have 10 balls, you're only going to have 9 cells. If you want a cell for each of your objects you should always return the count as is.
Sometimes, you may have to override the following function
override func prepareForReuse(){
super.prepareForReuse()
// reset to default value.
}
in class Shop1CollectionViewCell to guarantee cells will behave correctly. Hope you got it.
I was wondering if there is any problem with extending UIButton in order to have a ref of my Data?
For instance I have a table view and whenever user clicks on a cell I need to provide the user with the data for that specific cell.
Data is kept inside an array and array index was saved in UIButton tag but as my array gets updated, wrong indexes will be provided. So what i was trying to do was to extend a uibutton and then have a variable which holds my model.
This idea works fine but as Im not really experienced in swift I wanted to know what are the drawbacks and problems of doing such a thing.
You don't need to save the index as Button's tag. Subclassing the UIButton as Sneak pointed out in comment is clearly a very bad idea. On top of that saving your model in a UIComponent is disasters design.
You can do it multiple ways. One that I find neat :
Step 1:
Create a Class for your Custom Cell. Lets say MyCollectionViewCell. Because your Cell has a button inside it, you should create IBAction of button inside the Cell.
class MyCollectionViewCell: UICollectionViewCell {
#IBAction func myButtonTapped(_ sender: Any) {
}
}
Step 2:
Lets declare a protocol that we will use to communicate with tableView/CollectionView's dataSource.
protocol MyCollectionViewCellProtocol : NSObjectProtocol {
func buttonTapped(for cell : MyCollectionViewCell)
}
Step 3:
Lets create a property in our MyCollectionViewCell
weak var delegate : MyCollectionViewCellProtocol? = nil
After step 3 your MyCollectionViewCell class should look like
protocol MyCollectionViewCellProtocol : NSObjectProtocol {
func buttonTapped(for cell : MyCollectionViewCell)
}
class MyCollectionViewCell: UICollectionViewCell {
weak var delegate : MyCollectionViewCellProtocol? = nil
#IBAction func myButtonTapped(_ sender: Any) {
self.delegate?.buttonTapped(for: self)
}
}
Step 4:
In your tableView's CellForRowAtIndexPath or CollectionView's sizeForItemAtIndexPath confirm pass ViewController as delegate to cell.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell : MyCollectionViewCell = //deque your cell
(cell as! MyCollectionViewCell).delegate = self
}
Step 5:
Now make your ViewController confirm to protocol
class YourViewController: UIViewController,MyCollectionViewCellProtocol {
Step 6:
Finally implement method in your VC
func buttonTapped(for cell: MyCollectionViewCell) {
let indexPath = self.collectionView?.indexPath(for: cell)
//access array now
}
P.S:
Sorry though I know you are using TableView, in a hurry I have written code for CollectionView but the delegates are pretty same :) I hope you will be able to understand the logic
I currently have a Viewcontroller which does the heavy lifting of retrieving the data. I then need to push this data to my UICollectionView inside of a UIContainerView. I have tried pushing via a segue but I need to keep refreshing the data so I keep getting the error:
'There are unexpected subviews in the container view. Perhaps the embed segue has already fired once or a subview was added programmatically?'
I then went on to investigate delegates and made a protocol to share the data between the two classes, however how can I initiate a a reloadData function from my initial view controller?
I'm still very new to delegates but I've setup the following:
protocol ProductsViewControllerDelegate {
var catalogue : CatalogueData {get set}
}
I then get my first view controller to inherit this delegate and pass data to it. I then created a delegate instance in the collection view and at the segue I set the collectionview.delegate = self however how do I refresh the data when I pass new data to it?
UPDATE
So I believe my problem is that the data isn't passing in the first place. My setup is as follows:
View controller:
struct CatalogueData {
var data : NSArray
var numberOfRows : Int
var numberOfColumns : Int
var tileName : XibNames
}
class ProductsViewController:UIViewController, CollectionCatalogueDelegate{
internal var catalogue: CatalogueData!
override func viewDidLoad() {
super.viewDidLoad()
let catdta : CatalogueData = CatalogueData(data: ProductCodes,
numberOfRows: 2,
numberOfColumns: 4,
tileName: XibNames.ProductDisplayTall)
self.catalogue = catdta
}
}
The second collection view inside of the container view is setup like so:
protocol CollectionCatalogueDelegate {
var catalogue : CatalogueData! {get set}
}
class CollectionCatalogue: UICollectionViewController, UICollectionViewDelegateFlowLayout{
#IBOutlet var cv: UICollectionView?
var delegate : CollectionCatalogueDelegate?{
didSet{
cv?.reloadData()
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return (self.delegate?.catalogue.data.count)!
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
self.cv?.register(UINib(nibName: (self.delegate?.catalogue?.tileName.rawValue)!, bundle: nil), forCellWithReuseIdentifier: (self.delegate?.catalogue?.tileName.rawValue)!)
let c:ProductCollectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: selectedXib.rawValue, for: indexPath) as! ProductCollectionCell
c.layer.borderColor = UIColor.black.cgColor
c.layer.borderWidth = 0.5
c.layer.cornerRadius = 3
let mng = (self.delegate?.catalogue?.data)![indexPath.row] as? NSManagedObject
'...........DO STUFF WITH DATA HERE ETC
}
}
And dependant on other actions I will then want to update the 'catalogue' in the first view controller and it display the results on the collection view.