Using Autolayout constraints from Storyboard in UICollectionViewCell - ios

I'm using a custom UICollectionViewCell class for my UICollectionView. I have to use addSubview in my custom class because I'm using FirebaseUI-iOS. This is what my MessageCollectionViewCell looks like:
import Foundation
import UIKit
class MessageCollectionViewCell: UICollectionViewCell {
#IBOutlet var messageContainerView: UIView?
#IBOutlet var messageText: UILabel?
#IBOutlet var messageDisplayName: UILabel?
#IBOutlet var messageUserImage: UIImageView?
#IBOutlet var messageUserImageOverlay: UIView?
override init(frame: CGRect) {
super.init(frame: frame)
// Custom initialization code for label
let size = self.contentView.frame.size
let frame = CGRectMake(0.0, 0.0, size.width, size.height)
self.messageContainerView = UIView(frame: frame)
self.messageUserImageOverlay = UIView(frame: frame)
self.messageText = UILabel(frame: frame)
self.messageDisplayName = UILabel(frame: frame)
self.messageContainerView!.addSubview(self.messageUserImageOverlay!)
self.messageContainerView!.addSubview(self.messageText!)
self.messageContainerView!.addSubview(self.messageDisplayName!)
self.contentView.addSubview(messageContainerView!)
}
required init(coder aDecoder: NSCoder) {
println("Init")
super.init(coder: aDecoder)
}
}
I have constraints in my Storyboard file which I want to use, but when I'm using addSubview my constraints does not get used. Is there anyway I can use addSubview() and still keep the constraints? I know that I can add it programmatically, but I wish to use the constraints that i've already set inside Storyboard.

This issue is due to how FirebaseUI 'FirebaseCollectionViewDataSource` registers it's classes. I don't believe it's possible to instantiate a class like this and get the autolayout properties from a XIB like you're asking, but it is possible to solve the problem a layer back by fixing our handling of prototype cells.
The problem here is that we're registering the cell reuseIdentifier twice: once in the storyboard and once in code (FirebaseCollectionViewDataSource must do this in order to dequeue cells). Since we call ours second, it overwrites the first one, which means that none of your outlets are populated, layouts are weird, etc. This means that you have to set them up as if you were using regular subclasses rather than XIBs. The quickest thing you could do here is just use a XIB instead of a prototype cell (which is just a XIB inside the storyboard). So, how can we support FirebaseUI + prototype cells...
The short answer is that currently this feature isn't possible due to Apple's design of UICollectionView.
Unlike UITableView, which can check for this behavior by dequeuing a cell (which would return an instantiated prototype cell and tell us that the reuseidentifier has already been created) like we do in FirebaseTableViewDataSource here, UICollectionView doesn't provide a similar method, it only gives:
func dequeueReusableCellWithReuseIdentifier(_ identifier: String,
forIndexPath indexPath: NSIndexPath!) -> AnyObject
Given that this method requires an a indexPath as it has to return a non-nil object, it will throw an NSInternalInconsistencyException when we try to read an arbitrary object at initialization (since there exist no items for it to read from). Additionally, there doesn't appear to be any way to programmatically check if a reuseIdentifier is in use. This leaves us with a few options:
Recommend people not use prototype cells and instead do custom subclasses or XIBs and hook them in. Storyboards and prototype cells feel a little more brittle (mostly for reasons like this), but definitely have ease of use going for them.
Pull the -registerClass: forReuseIdentifier: call out of FirebaseCollectionViewDataSource, though this means that FirebaseTableViewDataSource should be changed as well (even though it can work) and make the developer explicitly call this (or not in the case of using a Storyboard).
Add a parameter for storyboards to the initialization call which would still retain the reuseIdentifier to dequeue cells, but not register the class.
Try to dequeue a cell, catch the NSException, register the class and try again. This works, but it throws the exception still and adds a little more code in the runloop (we have to wrap the call with try-catch that we know will fail at most once).
My personal preference is 1, but the value proposition for prototype cells is high enough that 3 might be the best option for this library. I'd rather not do 2, since cell registration is a difficult enough problem.
For now, I'd recommend using XIBs instead of prototype cells, or waiting for us to pick one of the above solutions (we can push a release pretty quickly to solve the problem).

Related

Cannot override mutable property 'collectionView' of type 'UICollectionView?' with covariant type 'GeminiCollectionView?'

I have had a custom collection view and everything worked fine. I wanted to include 'Gemini' to make beautiful animations when sliding my cells horizontally. I believe that the problem is not Gemini, I think the same would have happen with changing VC class to any other one, but I dont know how to solve this because I never faced with this before, is there a shortcut from this?
I have installed and imported the pod into code.
I had a UICollectionViewController but to work with gemini I needed to connect my collectionView from Storyboard to ViewController. Before that, I have put a class in Storyboard for my CollectionView to be GeminiCollectionView like in the image below:
After that, I changed a class for my CollectionView in ViewController, too, like in the image below and got those three errors:
In the later code, it doesnt show any errors:
viewDidLoad
collectionView.gemini
.rollRotationAnimation()
.degree(45)
.rollEffect(.rollUp)
cellForItemAt
self.collectionView.animateCell(cell)
scrollViewDidScroll
self.collectionView.animateVisibleCells()
willDisplay cell
if let cell = cell as? RatingCell {
//RatingCell inhertis the GeminiCell class
self.collectionView.animateCell(cell)
}
So, all the code is fine except the declaration as I mentioned at the beginning and here:
#IBOutlet var collectionView: GeminiCollectionView!
Waiting for random guys to unvote this :P
Solved
#IBOutlet var collectionView: GeminiCollectionView!
That line was a problem. Seems like you mustn't default Swift's name to override mutable property, which is collectionView for Collection Views and tableView for Table Views.
I changed that line to
#IBOutlet var collView: GeminiCollectionView!
...and the errors disappeared.

Embedding UIContainerView into table view and trying to access via #IBOutlet results in Unexpectedly found nil

Using Apple's rather old Swift Getting Started tutorial as a base, I've a working app. I'd like to add a UIView at the top of a table, to appear temporarily if an error occurs. Unfortunately, when trying to access UI elements from the UIView's associated class, I get an "Unexpectedly found nil while implicitly unwrapping an Optional value" error.
https://imgur.com/wfqFach.png
This is actually a UIContainerView, but apparently appears as a UIView.
I've subclassed both the UIView and the linked view (via an embed segue) with a subclass I called ErrorView, and added an #IBOutlet link to the UIView in the table view's subclass. The UI elements are linked to the ErrorView class with an #IBOutlet.
When accessing ErrorView class members via the #IBOutlet in the table subclass, everything is dandy until the ErrorView class tries to access its #IBOutlet linked elements, at which point it crashes with "Unexpectedly found nil while implicitly unwrapping an Optional value."
Strangely, accessing the view itself from within the Error Class is possible, and as such I'm able to do something view-related like change its colour without an error - though the colour change doesn't actually happen.
A very trimmed down version of the code is below, which should reproduce the issue.
I'm using iOS13 and Xcode 11 beta.
ErrorView.swift
import UIKit
class ErrorView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
#IBOutlet weak var uiMessage: UILabel!
func setErrorMessage(errorMessage: String){
uiMessage.text=errorMessage //<-- error here
}
}
ModuleTableViewController
import UIKit
import Foundation
class ModuleTableViewController: UITableViewController {
#IBOutlet weak var errorView: ErrorView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
errorView.setErrorMessage(errorMessage: "Hello")
}
}
In fact, container views contain View Controller not UIView, you need to set a UIViewController class to your error viewController, then make an outlet for you UIlable, then set a protocol and make you Error View controller conforms to it, when something will happen, it will update the label text.
Dragging a view onto the tableView creates a .tableHeaderView. You could use a UIContainerView holding the view from another view controller, but unless you intend to have a lot going on that would need to be handled that way, you're better off using a simple UIView.
Try it step-by-step:
1) Standard UITableViewController, with your code ready to go (assign the custom class of the controller to TestTableViewController):
2) Add a UIView to the table view. Give it a background color to make it easy to see:
3) Add a UILabel to that view. Give it a background color to make it easy to see. Constrain it 8-pts on all 4 sides:
4) Set the Custom Class of that view to your ErrorView class:
5) Connect the #IBOutlets. Click-drag (not Ctrl) from the circle in the code window to the object. You may find it easier to drag to the object in the Outline pane (so you don't accidentally connect the Label when you're trying to connect the View, for example):
6) It should look like this now (filled-in circles in the code window indicate the outlets have been connected):
7) Run the app, and you should get this:

Swift Custom Cell creating your own Cell with labels

I've just started to use Swift as a prorgamming language and i've run into a problem with Custom cells.
When i try to create custom cells, and then go forward and try to design them the way i need them ( with Style set to Custom ) everything looks good. Now i don't know how to put specific data into them, since all tutorials i found used the style option "basic" where they only have a text label to which they assign their data.
Now for me, when i "control drag" my labels into my code, i give them specific names such as "dateLabel" or "sourceLabel" in order to insert the data correctly.
now i'm not sure, and couldn't find any answers that worked, on how to recall my custom made labels so that i can assign my data to them...
Maybe someone of you could help me with this, since i'm pretty sure it's a simple problem but i coudln't find any resources to this ^^
hopefully the font isn't to small, i just wanted you guys to see the erros i get.
I used the following tutorial as a guide line, since it was the only one that worked just the way this guy did it: https://www.youtube.com/watch?v=0qE8olxB3Kk
I checked the identifier and he is set correctly and i can't find anything online on how i have to properly refer to my own labels with their correct names.
any help would be appreciated :)
Try the following steps:
Create a custom table view cell class that extends UITableViewCell. In my example, the custom table view cell class is called MyCustomTableViewCell.
Update your storyboard's cell so that it uses your custom table view cell class. Go to the Identity Inspector and set the Class value to the name of your custom table view cell class.
Update your storyboard's cell and give it a reuse identity value. Go to the Attributes Inspector and set the Identifier value. For example, I gave my cell an Identifier value of MyCustomCell.
Control drag the cell's labels into your new custom table view cell class (i.e., the MyCustomTableViewCell class).
Once you have done the above steps, you will be able to access the labels when you dequeue your cell in the tableView:cellForRowAtIndexPath: method. As the code snippet below shows, you will need to: 1) get the cell using the reuse identifier you established in the steps above and 2) cast to your custom table view cell class.
For example, here's what your custom table view cell would look like if you named it MyCustomTableViewCell. This is after you created the class and control dragged your labels into this class.
class MyCustomTableViewCell: UITableViewCell {
#IBOutlet weak var categoryLabel: UILabel!
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var sourceLabel: UILabel!
#IBOutlet weak var titleLabel: UILabel!
}
Your ViewController could look like this:
// NOTE: I subclassed UITableViewController since it provides the
// delegate and data source protocols. Consider doing this.
class ViewController: UITableViewController {
// You do NOT need your UILabels since they moved to your
// custom cell class.
// ...
// Omitting your other methods in this code snippet for brevity.
// ...
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Use your cell's reuse identifier and cast the result
// to your custom table cell class.
let article = tableView.dequeueReusableCellWithIdentifier("MyCustomCell", forIndexPath: indexPath) as! MyCustomTableViewCell
// You should have access to your labels; assign the values.
article.categoryLabel?.text = "something"
article.dateLabel?.text = "something"
article.sourceLabel?.text = "something"
article.titleLabel?.text = "something"
return article
}
}

Custom header of table view with IB

I would like to create a custom header for a table view section with interface builder. I cannot use the method titleForHeaderInSection because i need to display two labels. I used this instructions: Customizing Header and Footer of TableView in iOS8 but it does not work.
What I have done so far:
Create a custom class CustomTableCell which inherits from UITableViewCell
class CustomTableCell: UITableViewCell {
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var dateDescriptionLabel: UILabel!
}
Create a dynamic prototype cell in storyboard
Add an identifier
Connect the labels to the custom UITableViewCell
Implement viewForHeaderInSection
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("CustomTableCell") as CustomTableCell
headerCell.backgroundColor = UIColor.cyanColor()
headerCell.dateLabel.text = "Test date"
headerCell.dateDescriptionLabel.text = "Test date description"
return headerCell
}
When i run the app the section appears one second and then moves under the table cells and I get an error: no index path for table cell being reused.
What is wrong with this solution? I downloaded the project from the tutorial I have linked and it works there.
Your problem has its roots in how UIKit handles UITableViews.
To make sure table views are fast and responsive, even with a large amount of cells, the cells are re-used.
By calling tableView.dequeueReusableCellWithIdentifier("CustomTableCell") you are asking the tableView to give you a cell to reuse.
A lot of people have been using the reusable cells to design their headers/footers in Storyboards. Ever since iOS 7 Beta 5 this may lead to errors. This answer explains the situation well: What is the meaning of the “no index path for table cell being reused” message in iOS 6/7?
To design your own custom header/footer views I don't recommend using UITableViewCells. Instead you should create and design a custom UIView directly in your code. If you want to use Interface Builder you could create a .xib or create a view in your Storyboard that is not a subview of the actual controller's view.

Swift - Initialize a subclass of UIView

I have an already setup view and want to wrap it in a subclass of UIView.
class ElementView: UIView {
var selected = false
}
The problem is that I cannot initialize ElementView with the already existing view.
Swift doesn't allow assigning to self too.
init(view: UIView) {
//here I would have to call super.init(coder: NSCoder?) or super.init(frame: CGRect)
//none of which actually properly initializes the object
}
Any ideas how to implement this?
Thanks :)
Clarification:
I will give you the larger context hoping it'd be more clear:
I am implementing a UIScrollView subclass. The scroll view contains an array of UIView objects which are added externally (from the user of the class).
In my custom UIScrollView class I want to implement a tap gesture recognizer for each object. That's already done:
let singleTap = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
singleTap.cancelsTouchesInView = false
addGestureRecognizer(singleTap)
and the handler:
func handleTap(recognizer: UITapGestureRecognizer) {
//some code to handle the tap
}
The problem arises when I want to handle the tap, I want to store the previous state of the view (was it tapped before or not) in order to toggle that state when the tap happens. I want to do different stuff to the UIView depending on its state.
recognizer.view returns the view to which the recognizer is attached to, which is what I need. But, this way I have no possibility of implementing a state for the UIView.
That's why I wanted to implement a custom wrapper for UIView which should contain the state information (which is also a problem). That's how I came up to asking this question...
In order to create a custom init for the UIView subclass you have to call the required init for the UIView superclass. Also while creating the custom init you have to send the Frame of the view to the superclass. Upon fulfilling these requirements you are free to pass on any arguments to the newly created init including the tap recognizer info.
Remember, if you are creating any variables - they have to be not nil upon the creation of the instance, thus in variable declaration you have to create some initial argument (for example - 0 for Int, etc.) for the initializer to work. Here is the example code:
var variableOne: Int = 0
var variableTwo: Int = 0
init(variableOne: Int, variableTwo: Int) {
self.variableOne = variableOne
self.variableTwo = variableTwo
super.init(frame: CGRectZero)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
It sounds like you are trying to mimic a copy constructor, and it sounds like you are trying to build it in IB. The short answer is what you are doing doesn't make sense. It further sounds like that you wanted your code above to own or assume the identity of the argument view (your reference to not being able to assign to self). If this assumption is correct, your code would make even less sense - you would just need to assign to a variable.
Just create the view class, with the code that you have posted, you do not need to implement a constructor, since you have provided a default value for your selected variable. Your will with then be populated from IB via the coder based constructor.
If you are trying to clone or copy a given view, then refer to "Can UIView be copied"

Resources