I have a collectionViewCell, I added to it one label, I set constraint spacing to nearest neighbor to (0,0,0,0), so the must take all cell and adjust to its height and width, but the label is not showing at all.
I realized that what ever I put in a collectionCell, if I added to it a constraint, It well not be showing, so I ended up deleting all constraints and manually set label's height and width
CollectionViewCell:
import UIKit
class CollectionViewCell: UICollectionViewCell {
var text:String?
var delegate: TableViewCell?
#IBOutlet weak var label: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
loadFromNib() // load xib
let tap = UITapGestureRecognizer(target: self, action: #selector(tapFunc))
label.isUserInteractionEnabled = true
label.addGestureRecognizer(tap)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func tapFunc(_ sender: Any) {
// head will not be clickable cuz parent here gonna be nil
if let p = delegate {
if p.isUserInteractionEnabledWith(cell: self){
p.didTapeLabel(for: self, value: label.text!)
}
} else {
print("parent at collectionview cell is nil")
}
}
func fillOutData(_ text:String) {
label.text = text
}
}
enter image description here
I am building a multicolumn tableView :
tableView --> tableViewCell --> CollectionView --> CollectionViewCell --> label
Set data source and delegate for UICollectionView inside UITableViewCell.
override func awakeFromNib() {
//collection View Delegates
self.collectionView.delegate = self
self.collectionView.dataSource = self
super.awakeFromNib()
}
Related
Created a custom UIView 'MyLabelView' with two elements, 1x UILabel, 1x UIView which will be set to a background color.
MyLabelView
View:
Code:
import UIKit
#IBDesignable
class MyLabelView: UIView {
#IBOutlet var contentView: UIView!
#IBOutlet weak var imageView: UIView!
#IBOutlet weak var titleLabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
contentView?.prepareForInterfaceBuilder()
}
private func commonInit() {
let bundle = Bundle(for: type(of: self))
bundle.loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)
addSubview(contentView)
}
}
I'm using MyLabelView on two occasions:- MasterViewController (TableViewController with dynamic prototypes)- DetailViewController (TableViewController with static cells and grouped style)
MasterViewController
Shows MyLabelView correct as part of a custom UITableViewCellView:
Code (in cellForRowAt):
let cell: MyTableViewCell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell") as! MyTableViewCell
cell.myLabelView.titleLabel.text = "Label"
cell.myLabelView.imageView.backgroundColor = UIColor.red
// Dynamic width to fit label texts of various sizes
var frm: CGRect = cell.myLabelView.titleLabel.frame
frm.size.width = cell.myLabelView.titleLabel.intrinsicContentSize.width
cell.myLabelView.titleLabel.frame = frm
DetailViewController
Here I would like to render MyLabelView with increased font size (working) and right-aligned so that no matter how long the Label text is, it is always aligned next to the chevronView:
Code (in viewDidLoad):
myLabelView.titleLabel.text = "Label"
myLabelView.titleLabel.font = myLabelView.titleLabel.font.withSize(17)
myLabelView.imageView.backgroundColor = UIColor.red
// Dynamic width to fit label texts of various sizes
var frm: CGRect = myLabelView.titleLabel.frame
frm.size.width = myLabelView.titleLabel.intrinsicContentSize.width
myLabelView.titleLabel.frame = frm
Expected results
View:
View:
I tried to play around with additional, outer UIViews as containers and using AutoLayout, but somehow it never worked as I expected it. Thank you very much for your help.Swift 4, iOS11
UPDATE
I tried to set the constraints in IB as I don't have access to the cell (using static cells).Content View:
Title:
MyLabelView:
Just add a function to the UITableViewCell subclass and call it when you need to set the view next to the right side:
func setTrailingAnchorForView() {
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: imageView.leadingAnchor, constant: 0).isActive = true
}
Call it like this:
cell.setTrailingAnchorForView()
I have a requirement on showing a banner view to show a message,
Now the message's content may vary and the view should also resize depending on UILabels content.
UILabel is set to Word Wrap and numberOfLines is set to 0
The design in xib is,
And the respective class file is,
import UIKit
class ORABannerView: UIView {
#IBOutlet weak var bannerText: UILabel!
static func instantiate(message: String) -> ORABannerView {
let view: ORABannerView = initFromNib() ?? ORABannerView()
view.bannerText.text = message
return view
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
super.draw(rect)
}
}
The initFromNib is an implemented as UIView's extension,
extension UIView {
class func initFromNib<T: UIView>() -> T? {
guard let view = Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)?[0] as? T else {
return nil
}
return view
}
}
Tried with layoutIfNeeded() on the view, but it's not working for me.
Any suggestions will be appreciated.
You may want to try the following code,
Create a method sizeHeaderToFit and which returns height of the UIView based on its content's height.
private func sizeHeaderToFit(headerView: UIView?) -> CGFloat {
guard let headerView = headerView else {
return 0.0
}
headerView.setNeedsLayout()
headerView.layoutIfNeeded()
let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
return height
}
Then you can call the above method from the respective code,
sizeHeaderToFit(headerView: yourView)
In ORABannerView class, add override layoutSubviews,
override func layoutSubviews() {
super.layoutSubviews()
bannerText.preferredMaxLayoutWidth = bannerText.bounds.width
}
And in xib file, keep the constraints to its superview rather than safe area for code optimisation. [Optional]
Hope it helps.
Remove trailing OR leading constraints
set the size to fit property programmatically
I expect the class to look like
class ORABannerView: UIView {
#IBOutlet weak var bannerText: UILabel!
static func instantiate(message: String) -> ORABannerView {
let view: ORABannerView = initFromNib()
view.bannerText.text = message
return view
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
super.draw(rect)
}
}
let v = ORABannerView.instantiate(message: "djhhejjhsdjhdjhjhdshdsjhdshjdhjdhjdhjddhjhjdshjdhdjshjshjdshjdshjdshjdshjdsjhdshjds")
self.view.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: self.view.topAnchor,constant:50),
v.leadingAnchor.constraint(equalTo: self.view.leadingAnchor,constant:0),
v.trailingAnchor.constraint(equalTo: self.view.trailingAnchor,constant:0)
])
New users autolayout, always forgot about two useful constraints:
func setContentCompressionResistancePriority(UILayoutPriority, for: NSLayoutConstraint.Axis)
func setContentHuggingPriority(UILayoutPriority, for: NSLayoutConstraint.Axis)
It also exists in IB too:
Try to play with the priority parameter
I have implemented reusable button component using combination of .xib and cocoa class following this guide
It works, but there is an issue. In order to use it in one of my main View Storyboards I have to first drag in a normal view (referenced as superview in question title) and then apply my Button class, to make it a button.
This works, but initial view height and width alongside its white background persist, so I have to always manually rewrite those when I use my component, which in itself results in poor reusability.
Ideally, I'd like to drag in a view, set it to Button class and thats it, that view should instantly take buttons height and width and have transparent background. Is something like this achievable?
To light more context on this issue here are few useful bits of my implementation
1. Reusable component made as a .xib view and its own cocoa class
2. Contents of Button.swift
import UIKit
#IBDesignable class Button: UIView {
#IBOutlet var contentView: UIView!
#IBOutlet weak var label: UILabel!
#IBInspectable var buttonLabel: String? {
get {
return label.text
}
set(buttonLabel) {
label.text = buttonLabel
}
}
override init(frame: CGRect) {
super.init(frame: frame)
componentInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
componentInit()
}
private func componentInit() {
let bundle = Bundle(for: Button.self)
bundle.loadNibNamed("Button", owner: self, options: nil)
addSubview(contentView)
contentView.frame = self.bounds
contentView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
}
}
3. Example usage (inside one of my main view storyboards) demonstrating how ordinary view is turned into a button, but has issues with height, width and background color
P.S ^ if its hard to tell what is going on in a gif above, I basically drag UIView into a story board and give it custom class attribute of Button, this turns that view into a button.
EDIT: Just to make it clearer, my question is: Can I apply width, height and transparent colour to my XIB's parent / super view? End goal here is to just drag in a view onto storyboard, give it custom class of a Button and thats it, it should be sized properly and have transparent background, as opposed to how it is at the moment (view doesn't get sized as button and has white background)
You have to pin your subviews in Button properly and also in Main.storyboard. Then your custom view will autosize. And clear color is working.
import UIKit
#IBDesignable
class Button: UIView {
#IBOutlet var contentView: UIView!
#IBOutlet weak var label: UILabel!
#IBInspectable
var backColor: UIColor = .clear {
didSet {
backgroundColor = backColor
contentView.backgroundColor = backColor
}
}
override var backgroundColor: UIColor? {
get {
return backgroundColor
} set {
}
}
#IBInspectable
var buttonLabel: String? {
get {
return label.text
}
set(buttonLabel) {
label.text = buttonLabel
}
}
override init(frame: CGRect) {
super.init(frame: frame)
componentInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
componentInit()
}
private func componentInit() {
let bundle = Bundle(for: Button.self)
bundle.loadNibNamed("Button", owner: self, options: nil)
addSubview(contentView)
contentView.frame = bounds
backgroundColor = backColor
contentView.backgroundColor = backColor
contentView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
// for static height
contentView.heightAnchor.constraint(equalToConstant: 70).isActive = true
}
}
To ensure you CustomView would size itself properly, you can use bottom constraint >= 0. After testing reset to equals.
I created a nib file with a custom collectionViewCell and attached to a viewController
class CustomCollectionView: UICollectionViewCell{}
Now I have to use the exact cell inside a tableView. I created a new nib file and viewController
class CustomTableView: UITableViewCell{}
and I copied the hole code of CustomCollectionView on it. every thing is working fine but I believe that it dose not make sense to copy the hole exact code of CustomCollectionView into CustomTableView and to use the exact same nib file but with a tableViewCell instead of collectionViewCell on it. Is there any way to optimize what I did?
As you said in a comment in suhit's answer, you can do this by using a common view in both the CollectionViewCell and TableViewCell subclasses. You don't need a ViewController since it adds extra overhead. A simple UIView is enough. Some code to show what I mean:
class CustomTableViewCell: UITableViewCell {
var customView: CustomView!
func awakeFromNib() {
super.awakeFromNib()
customView = CustomView()
customView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(customView)
customView.fillSuperview()
}
}
class CustomCollectionViewCell: UICollectionViewCell {
var customView: CustomView!
func awakeFromNib() {
super.awakeFromNib()
customView = CustomView()
customView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(customView)
customView.fillSuperview()
}
}
extension UIView {
func fillSuperview() {
guard let superview = superview else {
return print("no superview")
}
topAnchor.constraint(equalTo: superview.topAnchor).isActive = true
bottomAnchor.constraint(equalTo: superview.bottomAnchor).isActive = true
leftAnchor.constraint(equalTo: superview.leftAnchor).isActive = true
rightAnchor.constraint(equalTo: contentVisuperviewew.rightAnchor).isActive = true
}
}
A sample implementation for the CustomView class:
class CustomView: UIView {
func initialize() {
//...
}
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
}
If you wish to create your custom view in a xib that's also fine, but it's a little trickier. This is beyond the scope of the question but I'm just going to leave a link here in case you need it.
If you want to use same view then its better to use similar type view i.e. use collectionView at both places so that you can use the CustomCollectionViewCell in both ViewControllers. UICollectionView is highly customisable so you can do whatever you want to do with UITableView in UICollectionView as well.
I'm trying to understand how to properly subclass view which is loaded from a xib in Swift.
I've got TitleDetailLabel class which is subclass of UIControl. This class has titleLabel and detailLabel outlets which are UILabels.
class TitleDetailLabel: UIControl {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var detailLabel: UILabel!
override func awakeAfterUsingCoder(aDecoder: NSCoder) -> AnyObject? {
return NTHAwakeAfterUsingCoder(aDecoder, nibName: "TitleDetailLabel")
}
func setTitle(text: String) {
self.titleLabel.text = text
}
func setDetailText(text: String) {
self.detailLabel.text = text
}
}
XIB structure:
Placeholders
File's Owner: NSObject (not changed)
First Responder
Title Detail Label - UIView - TitleDetailLabel class
Label - UILabel - title label
Label - UILabel - detail label
In Storyboard I've got view controller and placeholder - simple UIView object with constraints.
I've created extension to UIView class to simplify swapping placeholder with object I am interested in. It works good with this TitleDetailLabel class. Here is how it looks:
extension UIView {
public func NTHAwakeAfterUsingCoder(aDecoder: NSCoder, nibName: String) -> AnyObject? {
if (self.subviews.count == 0) {
let nib = UINib(nibName: nibName, bundle: nil)
let loadedView = nib.instantiateWithOwner(nil, options: nil).first as UIView
/// set view as placeholder is set
loadedView.frame = self.frame
loadedView.autoresizingMask = self.autoresizingMask
loadedView.setTranslatesAutoresizingMaskIntoConstraints(self.translatesAutoresizingMaskIntoConstraints())
for constraint in self.constraints() as [NSLayoutConstraint] {
var firstItem = constraint.firstItem as UIView
if firstItem == self {
firstItem = loadedView
}
var secondItem = constraint.secondItem as UIView?
if secondItem != nil {
if secondItem! == self {
secondItem = loadedView
}
}
loadedView.addConstraint(NSLayoutConstraint(item: firstItem, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: secondItem, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
}
return loadedView
}
return self
}
}
I decided to create BasicTitleDetailLabel subclass of TitleDetailLabel class to keep there some configuration code and other stuff.
class BasicTitleDetailLabel: TitleDetailLabel {
override func awakeFromNib() {
super.awakeFromNib()
self.setup()
}
override init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
private func setup() {
self.titleLabel.textColor = UIColor.NTHCadetGrayColor()
self.detailLabel.textColor = UIColor.NTHLinkWaterColor()
}
}
But application crashes every time after I changed class of this placeholder from TitleDetailLabel to BasicTitleDetailLabel.
App crashes because titleLabel and detailLabel are nil.
How can I properly use this TitleDetailLabel class with xib and how to subclass this correctly? I don't want to create another xib which looks the same like the first one to use subclass.
Thanks in advance.
Make sure you make the set the File's Owner for the .xib file to the .swift file. Also add an outlet for the root view and then load the xib from code. This is how I did it for a similar project:
import UIKit
class ResuableCustomView: UIView {
#IBOutlet var view: UIView!
#IBOutlet weak var label: UILabel!
#IBAction func buttonTap(sender: UIButton) {
label.text = "Hi"
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NSBundle.mainBundle().loadNibNamed("ReusableCustomView", owner: self, options: nil)[0] as! UIView
self.addSubview(view)
view.frame = self.bounds
}
}
It is a little different than yours but you can add the init:frame method. If you have an awakeFromNib method then don't load the setup method in both awakeFromNib and init:coder.
My full answer for the above code project is here or watch this video.