why can't I create a lazy property accessing a tableView row? - uitableview

I want to create a cell property and access it. I'm creating it lazy because it can't be accessed during initialization of the tableView.
I placed it as a property of a my tableViewController subclass, but I'm getting the following error:
Ambiguous reference to member 'tableView(_:numberOfRowsInSection:)'
My code:
lazy var messageCell : CustomCell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 1, inSection: 1)) as! CustomCell
Yet if I post this exact line without the lazy inside a function it would all work fine...

lazy variables can only be declared as a member of a class or a struct. Set an #IBOutlet to your table. Let's call it tblView. Then you can declare a lazy instantiated variable like shown below.
lazy var cell: CustomCell = {
return self.tblView.cellForRow(at: IndexPath(row: 1, section: 1)) as! CustomCell
}()
Remember, the variable has to be a member of a class or a struct and not being declared inside a function

Related

Passing a newly initialized UIImageView to a property (type UIImageView) of a class give nil?

I have a CustomCollectionViewController and a CustomCollectionViewCell. I dragged and dropped a UIImageView onto the CustomCollectionViewCell in storyboard and bound that in code.
Then I tried to initialized the cell within collectionView(_:cellForItemAt:) with this line:
customCell.imageView = UIImageView(image: images[indexPath.item])
it didn't work, customCell.imageView was nil when collectionView(_:cellForItemAt:) returns. But with imageCell.imageView.image = images[indexPath.item], it worked.
Why is that so? Thanks.
Code snippet:
Inside class CustomCollectionViewController
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath)
if let imageCell = cell as? ImageCollectionViewCell, images[indexPath.item] != nil {
imageCell.imageView = UIImageView(image: images[indexPath.item]) // doesn't work, cell.imageView would be nil
imageCell.imageView.image = images[indexPath.item] // work
}
return cell
}
And:
class CustomCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var imageView: UIImageView!
}
Long answer:
Let's name your old ImageView (the one from storyboard) as A and new one (created at code) as B UIImageView(image: images[indexPath.item])
The variable will be called just reference
You have set your reference to be weak, which means, as soon as it loses any strong reference (like being in a view hierarchy) it is destroyed. When you assigned it to be B, that B wasn't assigned to anywhere else so it was immediately destroyed so your reference became nil. Your A is still on cell (in subviews array and arrays are tend to hold strong references) as long as you remove it from hierarchy. You have to understand difference between link reference (weak or strong) and being a subview in a view (strong). Before you made any changes in code, the A imageView had two links - weak pointer in your code and strong pointer in cell hierarchy. When you replaced reference with B, A lost weak, but still has strong reference in cell itself. B only had weak so it was immediately destroyed before any actual use of it
You have problems with your logic too. Just because you change reference to your B imageView, doesn't mean the image will appear in your cell. Your B needs to be set it's frame or constraints and added in some view hierarchy (cell). What you actually need is just change your A.image property to your new image and that's it imageCell.imageView.image = images[indexPath.item]
Please read more about memory management and weak, strong references
Short answer:
Remove this line
imageCell.imageView = UIImageView(image: images[indexPath.item])
So your code looks like
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath)
if let imageCell = cell as? ImageCollectionViewCell,
let image = images[indexPath.item]
{
imageCell.imageView.image = image
}
return cell
}
It's a bad idea to set the imageView property. This is an outlet, instantiated by the storyboard/xib. I'm guessing that the new UIImageView you create is not retained, since imageView is declared as weak.
You should avoid "recreating" any views or controls like this, if they are added using Interface builder in the first place.
Since the imageView is declared weak
#IBOutlet weak var imageView: UIImageView!
assigning a new instance to it makes it nil as lhs can't retain rhs , look to warning
If you want it to work then remove the weak as the default is strong
#IBOutle var imageView: UIImageView!
but it'll make no sense to your current case so just only assign the image
Like the other answers already mentioned the problem is that imageCell.imageView is weak, so it only references an ImageView that is retained strongly in another place.
imageCell.imageView is originally referencing the ImageView you dragged and dropped in the storyboard. When a View is contained in another View it is strongly retained.
If you want to replace that ImageView you need to create a strong reference to your new ImageView, for example adding it to another View, something like this:
let newImageView = UIImageView(image: images[indexPath.item])
imageCell.contentView.addSubview(newImageView)
imageCell.imageView = newImageView
But as others have mentioned before, probably is best that you just replace the UIImage of your current ImageView.

How to communicate between Classes in a hierarchy in Swift

With the inspiration coming from the idea that you can code anything, i tried my hand at a complicated CollectionView nested structure which goes as follows:
CustomCollectionViewController
--CustomCollectionViewCell
----CustomTableView
------CustomTableViewCell
--------CustomPickerView
In CustomCollectionViewController, the main data feed comes from property:
var cardFeed: [String: [Card]] = [:]
Card is my defined model and variable cardFeed is applied the usual way in UICollectionView Delegate & DataSource methods:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "card", for: indexPath) as! CustomCollectionViewCell
cell.contentView.clipsToBounds = true
cell.delegate = self
cell.card = self.cardFeed[string]![indexPath.row]
cell.cardIndex = indexPath.row
}
From the delegate methods, cardFeed above sets a main property in CustomCollectionViewCell used to update the interface:
var card: Card! {
didSet{
setupCard()
}
}
The property card is also the data feed for UITableView Delegate & Datasource.
Everything works perfectly, everything shows up as it should. Except for the fact that when a user picks values from the CustomPickerView, the main data feed, namely cardFeed defined in CustomCollectionViewController (shown above) must update!
My solution is this:
(1) Given that there are three components, define array that records changes in CustomPickerView selected rows and a call back method to pass down the variable:
var selectedRow: [Int] = [1, 0, 0] {
didSet {
if updateRow != nil {
updateRow!(self.selectedRow)
}
}
}
var updateRow: ( ([Int]) -> () )?
(2) In CustomCollectionViewCell define another call back with an extra argument, to keep track of what cell actually sent the selected row array :
var passSelectedRow: (([Int], Int) -> ())?
which is called in tableViews cellForRowAtIndexPath method:
cell.updateRow = { selectedRow in
self.passSelectedRow!(selectedRow, indexPath.row)
}
(3) finally update cardFeed in CustomCollectionViewController cellForItemAtIndexPath:
cell.passSelectedRow = { selectedRow, forIndex in
if self.cardFeed[string]![indexPath.row].chosenFood[forIndex].selectedRow != selectedRow {
self.cardFeed[string]![indexPath.row].chosenFood[forIndex].selectedRow = selectedRow
}
}
But here is the problem, if i now add a didSet to cardFeed, it will create an infinite loop because cellForRowAtIndexPath will be called indefinitely. If i get the CustomCollectionViewCell reference anywhere other than cellForItemAtIndexPath, self.collectionView?.reload() does not work! Is there a way i can update my variable cardFeed in CustomCollectionViewController from the selected rows in CustomPickerView?
When communicating between objects it is bad practice to make the child object have a strong reference to its owner, that is how you end up with retain cycles and bugs.
Let's take a look at the two most common ways of communicating between objects: delegation and notification.
with delegation:
Create a protocol for communicating what you want, in your example:
protocol PickerFoodSelectedDelegate : class {
func selected(row : Int, forIndex : Int)
}
Add weak var selectionDelegate : PickerFoodSelectedDelegate as a variable in the picker class
In the tableView class, during cellForItemAtIndexPath, you assign self to picker.selectionDelegate
You then create a similar structure for communicating between the table and the collection view.
The key part is that delegate references be declared as weak, to avoid retain cycles and bugs.
With notifications you can use NotificationCenter.default to post a notification with any object you want, in this case you would:
Subscribe to a notification name you choose in the table view.
Post a notification from the picker view when an option is chosen.
When the table receives the notification, extract the object.
Do the same from the table to the collection view.
Hope this helps!

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]
}
}

iOS FirebaseUI with Custom UITableViewCell

I'm trying to populate a UITableView with custom UITableViewCells using FirebaseUI. Everything seems to work except that the TableViewCells delivered by the FirebaseTableViewDataSource.populateCell() method are not "wired" to the outlets in the view. Here's an example:
This is the custom UITableViewCell class. The UITableView in the storyboard for the view controller being loaded has this custom cell class.
class JobTableViewCell : UITableViewCell {
#IBOutlet weak var labelJobName: UILabel!
}
Here's the code in ViewDidLoad() of the view controller that is setting up the table:
dataSource = FirebaseTableViewDataSource(ref : ref, cellClass : JobTableViewCell.self, cellReuseIdentifier: "JobTableViewCell", view: self.tableView);
dataSource.populateCell{(cell : UITableViewCell, obj : NSObject) -> Void in
let snapshot = obj as! FIRDataSnapshot;
let job = Job(snapshot);
let jobCell = cell as! JobTableViewCell
//////////////////////////////////////////////////////////
// jobCell and its outlets are nil so this next statement
// causes exception.
jobCell.labelJobName.text = job.name;
}
self.tableView.dataSource = self.dataSource;
So the question is how to get FirebaseUI to deliver the custom cell with the outlets wired up? I can't figure out how to do it.
My solution was to stop using FirebaseUI for iOS .
You need to use self.tableView.bind
For example,
var dataSource: FUITableViewDataSource!
self.dataSource = self.tableView.bind(to: ref) { tableView, indexPath, snapshot in
// Dequeue cell
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
/* populate cell */
return cell
}
See Firebase UI database for iOS for more info

Why am I getting EXC_BAD_INSTRUCTION error when I am trying to load my tableview in swift?

I am trying to populate my tableview after saving some values to my coredata database. I am asking it to display some of the values as indicated in my code, but whatever I do, I get the above error.
Any suggestions? I have tried some of the other posts with this problem, but nothing seems to work.
I cannot think where I might be giving out a nil output Swift is not expecting.
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell? {
let CellId: NSString = "Cell"
var cell: UITableViewCell = tableView?.dequeueReusableCellWithIdentifier(CellId) as UITableViewCell
if let ip = indexPath {
var data: NSManagedObject = myCustomers[ip.row] as NSManagedObject
var addno = data.valueForKeyPath("custaddno") as String
var addname = data.valueForKeyPath("custaddfirstline") as String
cell.textLabel.text = "\(addno) \(addname)"
var jcost = data.valueForKeyPath("custjobcost") as Float
var jfq = data.valueForKeyPath("custjobfq") as Float
var jfqtype = data.valueForKeyPath("custjobfqtype") as String
cell.detailTextLabel.text = "Charge: \(jcost) to be done every \(jfq) \(jfqtype)"
}
return cell
}
Problem
You might have forgotten to register a class for the cell identifier before. See documentation of dequeueReusableCellWithIdentifier::
Discussion
Prior to dequeueing any cells, call this method or the registerNib:forCellReuseIdentifier: method to tell the table view how to create new cells. If a cell of the specified type is not currently in a reuse queue, the table view uses the provided information to create a new cell object automatically.
The method returns nil then and Swift fails to implicitly unwrap the optional.
Solution
In code
To solve this problem, you could either call registerClass:forCellReuseIdentifier: (see docs) in viewDidLoad like this...
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
in Interface Builder
... or, if you're using Storyboards, set it directly in Interface Builder in the attributes inspector when the UITableViewCell prototype is selected:

Resources