I want to add a tab recognizer to a custom .xib file view in my ios swift app. Here's the code from the owner class of the .xib file:
import UIKit
class WordLabel: UILabel {
#IBOutlet weak var wordLabel: UILabel!
#IBOutlet var wordFrame: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
//I added the tab recognizer here
wordLabel.isUserInteractionEnabled = true
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(labelClicked(_:)))
wordLabel.addGestureRecognizer(gestureRecognizer)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
Bundle.main.loadNibNamed(K.wordLabelNibName, owner: self, options: nil)
addSubview(wordFrame)
wordFrame.frame = self.bounds
wordFrame.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
//should happen when label is tapped
#objc func labelClicked(_ sender: Any) {
print("UILabel clicked")
}
}
When I ran the project on a phone simulator, there were no errors.
App Running On The Simulator
But when I clicked the labels that showed up, the message was not printed onto the console (meaning that the action was not triggered). What am I doing wrong?
Thank you for your help.
github link to my project:
https://github.com/ShaungYi/PolyLibrum/blob/main/PolyLibrum/View/BookReader/WordLabel/WordLabel.swift
When you use WordLabel custom UILabel with xib, init(frame:) constructor is not called. init(coder:) is called instead. Because of that, your gesture recognizer doesn't work. You must to move the gesture recognizer assignment to commonInit() method.
I solved the problem on my own. It seems that I set the owner class of the .xib file to inherit from a UILabel. When I changed the superclass to the generic UIView, everything worked out perfectly! I don't really understand how this fixed the problem, but my theory is that previously, the owner class relegated the UIGesture event to the UILabel class instead of itself, thus not triggering the bound gesture recognizer. Now That I diverted that to the owner class, there's no such problem.
So for anyone else who has my problem- set the owner class' superclass to the generic UIView if possible?
Thanks again to Furkan Kaplan for his help.
Related
I'm attempting to use a custom uiview for a tinder-style card swipe library called Koloda, which requires that I pass in a uiview for each card in the stack. I'm attempting to use a custom uiview with relevant fields/info, and after thorough testing I've discovered that generating an instance of uinib is giving me a 'not key value coding-compliant' error, despite having all my outlet connections in order.
I've tried recreating all my IBOutlets, going as far as to just start over with a new xib file with new connections. When I drag a uiview onto a view controller in storyboard, and conform it to my custom type, it shows up no problem and I can manipulate its various properties as desired. It's only when instantiating the custom view that I get the error.
class VoteCard: UIView {
#IBOutlet var contentView: UIView!
#IBOutlet weak var proPicImg: UIImageView!
#IBOutlet weak var nameLbl: UILabel!
#IBOutlet weak var homeLbl: UILabel!
#IBOutlet weak var ratingLbl: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
Bundle.main.loadNibNamed("VoteCard", owner: self, options: nil)
contentView.fixInView(self)
}
class func instanceOf() -> VoteCard {
return UINib(nibName: "VoteCard", bundle: nil).instantiate(withOwner: nil, options: nil).first! as! VoteCard
}
}
If all works correctly, my Koloda card stack should show my custom cards, however as of yet I can't get passed the generation of a new instance.
Please see this answer: https://stackoverflow.com/a/52831749/2537616
Here is the idea:
Set the BaseClass of your custom view inside .xib file.
Connect outlet as usual.
Then create static helper to instantiate UINib.
There might be some outlets which was define first and then removed from your class. But they are not removed from your storyboard or xib. Please check for those outlets in your view and remove them. Please refer the image. Here buttonShow is not in my view controller so remove it.
Hope it helps.
I come from Obj-C and I'm struggling on doing something super basic in Swift!
I have a custom UICollectionViewCell:
class CustomCell: UICollectionViewCell
{
// Outlets
// ***************************
#IBOutlet weak var button: UIButton!
// Init
// ***************************
required init?(coder aDecoder: NSCoder)
{
super.init(coder:aDecoder)
setup()
}
override init(frame: CGRect)
{
super.init(frame: frame)
setup()
}
func setup()
{
button.backgroundColor = .white
}
}
The cell is loaded from an external .xib file, so init(coder:) is called for the initialization but my button is not ready.
If I change to button?.backgroundColor the app doesn't crash but obviously nothing happen.
I can call my setup() function in layoutSubviews() and it works, but it's definitely not the right place to be.
How do I solve this massive problem? lol
Edit
Probably I have to call setup() from awakeFromNib(), right?
I usually don't use external .xib, I'm not familiar with them
Edit: Sorry It seems youe edited your question before my answer, it seems as you load it from XIB, then you can run the awakeFromNib which will be called when you register a nib using this method:
Apple Source UICollectionView
Apple Source UITableView
--- old post below ---
In Xcode 6 you have to provide additional init(coder:) initializer in
classes like RDCell, which is the subclass of UICollectionViewCell.
This initializer is called instead of init(frame:) when the class gets
initialized from a storyboard or a xib file. That’s not our case, but
we still need to provide init(coder:). We can use the solution
provided to us by Xcode. In Issue Navigator click on an error that
says “'required' initializer 'init(coder:)' must be provided by
subclass of 'UICollectionViewCell'“,
Source
I'm using Swift and Xcode 6.4 for what it's worth.
So I have a view controller that will be containing some multiple pairs of UILabels and UIImageViews. I wanted to put the UILabel-UIImageView pair into a custom UIView, so I could simply reuse the same structure repeatedly within the aforementioned view controller. (I'm aware this could be translated into a UITableView, but for the sake of ~learning~ please bear with me). This is turning out to be a more convoluted process than I imagined it would be, I'm having trouble figuring out the "right" way to make this all work in IB.
Currently I've been floundering around with a UIView subclass and corresponding XIB, overriding init(frame:) and init(coder), loading the view from the nib and adding it as a subview. This is what I've seen/read around the internet so far. (This is approximately it: http://iphonedev.tv/blog/2014/12/15/create-an-ibdesignable-uiview-subclass-with-code-from-an-xib-file-in-xcode-6).
This gave me the problem of causing an infinite loop between init(coder) and loading the nib from the bundle. Strangely none of these articles or previous answers on stack overflow mention this!
Ok so I put a check in init(coder) to see if the subview had already been added. That "solved" that, seemingly. However I started running into an issue with my custom view outlets being nil by the time I try to assign values to them.
I made a didSet and added a breakpoint to take a look...they are definitely being set at one point, but by the time I try to, say, modify the textColor of a label, that label is nil.
I'm kind of tearing my hair out here.
Reusable components seem like software design 101, but I feel like Apple is conspiring against me. Should I be looking to use container VCs here? Should I just be nesting views and having a stupidly huge amount of outlets in my main VC? Why is this so convoluted? Why do everyone's examples NOT work for me?
Desired result (pretend the whole thing is the VC, the boxes are the custom uiviews I want):
Thanks for reading.
Following is my custom UIView subclass. In my main storyboard, I have UIViews with the subclass set as their class.
class StageCardView: UIView {
#IBOutlet weak private var stageLabel: UILabel! {
didSet {
NSLog("I will murder you %#", stageLabel)
}
}
#IBOutlet weak private var stageImage: UIImageView!
var stageName : String? {
didSet {
self.stageLabel.text = stageName
}
}
var imageName : String? {
didSet {
self.stageImage.image = UIImage(named: imageName!)
}
}
var textColor : UIColor? {
didSet {
self.stageLabel.textColor = textColor
}
}
var splatColor : UIColor? {
didSet {
let splatImage = UIImage(named: "backsplat")?.tintedImageWithColor(splatColor!)
self.backgroundColor = UIColor(patternImage: splatImage!)
}
}
// MARK: init
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if self.subviews.count == 0 {
setup()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
func setup() {
if let view = NSBundle.mainBundle().loadNibNamed("StageCardView", owner: self, options: nil).first as? StageCardView {
view.frame = bounds
view.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
addSubview(view)
}
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
}
*/
}
EDIT: Here's what I've been able to get so far...
XIB:
Result:
Problem: When trying to access label or image outlets, they are nil. When checking at breakpoint of said access, the label and image subviews are there and the view hierarchy is as expected.
I'm OK with doing this all in code if thats what it takes, but I'm not huge into doing Autolayout in code so I'd rather not if there's a way to avoid it!
EDIT/QUESTION SHIFT:
I figured out how to make the outlets stop being nil.
Inspiration from this SO answer: Loaded nib but the view outlet was not set - new to InterfaceBuilder except instead of assigning the view outlet I assigned the individual component outlets.
Now this was at the point where I was just flinging shit at a wall and seeing if it'd stick. Does anyone know why I had to do this? What sort of dark magic is this?
General advice on view re-use
You're right, re-usable and composable elements is software 101. Interface Builder is not very good at it.
Specifically, xibs and storyboard are great ways to define views by re-using views that are defined in code. But they are not very good for defining views that you yourself wish to re-use within xibs and storyboards. (It can be done, but it is an advanced exercise.)
So, here's a rule of thumb. If you are defining a view that you want to re-use from code, then define it however you wish. But if you are defining a view that you want to be able to re-use possibly from within a storyboard, then define that view in code.
So in your case, if you're trying to define a custom view which you want to re-use from a storyboard, I'd do it in code. If you are dead set on defining your view via a xib, then I'd define a view in code and in its initializer have it initialize your xib-defined view and configure that as a subview.
Advice in this case
Here's roughly how you'd define your view in code:
class StageCardView: UIView {
var stageLabel = UILabel(frame:CGRectZero)
var stageImage = UIImageView(frame:CGRectZero)
override init(frame:CGRect) {
super.init(frame:frame)
setup()
}
required init(coder aDecoder:NSCoder) {
super.init(coder:aDecoder)
setup()
}
private func setup() {
stageImage.image = UIImage(named:"backsplat")
self.addSubview(stageLabel)
self.addSubview(stageImage)
// configure the initial layout of your subviews here.
}
}
You can now instantiate this in code and or via a storyboard, although you won't get a live preview in Interface Builder as is.
And alternatively, here's roughly how you might define a re-usable view based fundamentally on a xib, by embedding the xib-defined view in a code-defined view:
class StageCardView: UIView {
var embeddedView:EmbeddedView!
override init(frame:CGRect) {
super.init(frame:frame)
setup()
}
required init(coder aDecoder:NSCoder) {
super.init(coder:aDecoder)
setup()
}
private func setup() {
self.embeddedView = NSBundle.mainBundle().loadNibNamed("EmbeddedView",owner:self,options:nil).lastObject as! UIView
self.addSubview(self.embeddedView)
self.embeddedView.frame = self.bounds
self.embeddedView.autoresizingMask = .FlexibleHeight | .FlexibleWidth
}
}
Now you can use the code-defined view from storyboards or from code, and it will load its nib-defined subview (and there's still no live preview in IB).
I was able to work it around but the solution is a little bit tricky. It's up to debate if the gain is worth an effort but here is how I implemented it purely in interface builder
First I defined a custom UIView subclass named P2View
#IBDesignable class P2View: UIView
{
#IBOutlet private weak var titleLabel: UILabel!
#IBOutlet private weak var iconView: UIImageView!
#IBInspectable var title: String? {
didSet {
if titleLabel != nil {
titleLabel.text = title
}
}
}
#IBInspectable var image: UIImage? {
didSet {
if iconView != nil {
iconView.image = image
}
}
}
override init(frame: CGRect)
{
super.init(frame: frame)
awakeFromNib()
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
override func awakeFromNib()
{
super.awakeFromNib()
let bundle = Bundle(for: type(of: self))
guard let view = bundle.loadNibNamed("P2View", owner: self, options: nil)?.first as? UIView else {
return
}
view.translatesAutoresizingMaskIntoConstraints = false
addSubview(view)
let bindings = ["view": view]
let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat:"V:|-0-[view]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: bindings)
let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat:"H:|-0-[view]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: bindings)
addConstraints(verticalConstraints)
addConstraints(horizontalConstraints)
}
titleLabel.text = title
iconView.image = image
}
This is how it looks like in interface builder
This is how I embedded this custom view in the example view controller defined on a storyboard. Properties of P2View are set in the attributes inspector.
There are 3 points worth mentioning
First:
Use the Bundle(for: type(of: self)) when loading the nib. This is because the interface builder renders the designables in the separate process which main bundle is not the same as your main bundle.
Second:
#IBInspectable var title: String? {
didSet {
if titleLabel != nil {
titleLabel.text = title
}
}
}
When combining IBInspectables with IBOutlets you have to remember that the didSet functions are called before awakeFromNib method. Because of that, the outlets are not initialized and your app will probably crash at this point. Unfortunatelly you cannot omit the didSet function because the interface builder won't render your custom view so we have to leave this dirty if here.
Third:
titleLabel.text = title
iconView.image = image
We have to somehow initialize our controls. We were not able to do it when didSet function was called so we have to use the value stored in the IBInspectable properties and initialize them at the end of the awakeFromNib method.
This is how you can implement a custom view on a Xib, embed it on a storyboard, configure it on a storyboard, have it rendered and have a non-crashing app. It requires a hack, but it's possible.
What is best strategy to load custom UIViews with XIB and Outlets? At this moment I have code listed below. I think this code is bad because I have 2 UIViews as container and in future probably problem with constraints.
UIViewController ( I don't want all outlets and actions in one big ViewController )
func showCategories() {
if(self.categoriesView == nil) {
self.categoriesView = CategoriesView()
}
self.view.addSubview(self.categoriesView!)
}
Custom UIView - CategoriesView
class CategoriesView, ...protocols... {
#IBOutlet var table:UITableView!
override init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame:CGRect) {
super.init(frame: frame)
}
override init() {
super.init()
let views = NSBundle.mainBundle().loadNibNamed("CategoriesView", owner: self, options: nil)
let view = views![0] as CategoriesView
self.frame = view.frame
self.addSubview(view)
}
....
}
In Apple's MVC, it's best to avoid views with too much logic in them. If you want to compose a complex view using component subviews, then look at Creating Custom Container View Controllers.
If you are already using storyboards, a container view will take care of most of the complexity for your.
When inheriting from UIControl my app crashes as soon as I add a property to my UIControl Class and a hit test is performed (EXC_BAD_ACCESS) => E.g. mouse over the Control:
class ReloadButton: UIControl {
var stopRotating: Bool = true
init(coder aDecoder: NSCoder!)
{
super.init(coder: aDecoder)
}
init(frame aRect: CGRect){
super.init(frame: aRect)
}
}
If I remove the property stopRotating it won't crash. If I change the inheritance to UIButton instead of UIControl the crash won't happen.
Is there a specific function which needs to be added to UIControl to handle hit tests?
UPDATE: I created a minimalistic sample project on github: https://github.com/Aranir/hit_test
With the beta 5 Xcode the error messages have become more explicit.
Apparently the method
override init(){
super.init()
}
needed to be implemented for it to work. This is now also necessary if the class inherits from UIButton