Set different horizontal alignment of a custom UIView - ios

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()

Related

Tap Gesture not working for custom view in swift

I have a circleButton, and adding a gesture recognizer to it is not working. circleButton is a UIView.
#IBOutlet private weak var circleButton: UIView!
Here is my code:
class CustomBottomBar: UIView {
#IBOutlet private weak var circleButton: UIView!
#IBOutlet private weak var bottomBarView: UIView!
#IBOutlet private weak var contentView: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
override func layoutSubviews() {
super.layoutSubviews()
circleButton.layer.borderColor = UIColor.black.cgColor
circleButton.layer.borderWidth = 2
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
#objc func onAddButtonClicked() {
print("add")
}
private func commonInit() {
if let customBar = Bundle.main.loadNibNamed("CustomBottomBar", owner: self, options: nil)?[0] as? UIView {
customBar.frame = bounds
addSubview(customBar)
circleButton.layer.cornerRadius = circleButton.frame.height/2
circleButton.clipsToBounds = true
circleButton.isUserInteractionEnabled = true
print(self.circleButton.frame.size)
print(self.contentView.frame.size)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onAddButtonClicked))
circleButton.addGestureRecognizer(tapGesture)
}
}
}
The frame size is also coming properly for circleButton. Not sure what is the problem. I am using customBottomBar in a storyboard. Here is the full code :
https://github.com/hirakjyotiborah/AwesomeTaskManag3r
Inside your Main.storyboard give a height constraint to your bottom Bar as you only set leading , trailing and bottom or do this inside commonInit
// 70 (Bottom bar) + 80/2 (half of circle button) = 110
heightAnchor.constraint(equalToConstant: 110).isActive = true
As the base for responding to any click is the frame and the main parent view of the button has zero height

NSLayout Constraint not working on Custom View

I am creating a custom UIView in swift, here is the code for the custom View,
class FormField: UIView {
#IBOutlet var viewLabel : UILabel!
#IBOutlet var textField: UITextField!
#IBOutlet var separator: UIView!
private var verticallyCenteredAnchor : NSLayoutConstraint!
private var topPositionedAnchor : NSLayoutConstraint!
private var isViewHighlighted = false
private var view: UIView?
let nibName = "FormField"
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
override class var requiresConstraintBasedLayout: Bool {
return true
}
func commonInit() {
guard let view = loadViewFromNib() else { return }
self.view = view
view.frame = self.bounds
self.addSubview(view)
verticallyCenteredAnchor = viewLabel.centerYAnchor.constraint(equalTo: self.view!.centerYAnchor)
verticallyCenteredAnchor.isActive = false
topPositionedAnchor = viewLabel.topAnchor.constraint(equalTo: self.view!.topAnchor,constant: 0)
topPositionedAnchor.isActive = true
let userNameTap = UITapGestureRecognizer(target: self, action: #selector(animateLabel))
userNameTap.numberOfTapsRequired = 1
view.addGestureRecognizer(userNameTap)
}
#objc func animateLabel() {
if(self.isViewHighlighted) {
verticallyCenteredAnchor.isActive = true
topPositionedAnchor.isActive = false
} else {
verticallyCenteredAnchor.isActive = false
topPositionedAnchor.isActive = true
}
self.isViewHighlighted = !self.isViewHighlighted
UIView.animate(withDuration: 0.5) {
self.view!.layoutIfNeeded()
}
}
func loadViewFromNib() -> UIView? {
let nib = UINib(nibName: nibName, bundle: nil)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
}
I am using a xib file for the view and constraints are set in that. Now when someone taps on the view I am trying to change the constraints with animation. But that's not working, no layout change is happening when the user taps on the view. I can confirm that the animateLabel() method is called when the user taps.
Now if is add in the line,
self.view!.translatesAutoresizingMaskIntoConstraints = false
all constraints are messed up, it's not honouring the constraints I already set in the xib.
What's going wrong here?
I am using a xib file for the view and constraints are set in that.
You have conflicts in your constraints who are in xib + in code , you need to hook the constraints in xib as outlets and play with their .isActive property and completely remove the code constraints

Storyboard xib live view renders all subviews in top-left corner

I am using XCode 12.1 and I am trying to create some reusable XIBs and want to render them in the storyboard. My problem is that all those views will be rendered in the top-left corner, just like if the frame of my loaded view is CGRect.zero. The view controller in the storyboard looks like this:
I have made a subclass of UIView that dynamically loads a view from .xib:
import UIKit
#IBDesignable
class NibView: UIView {
required init?(coder: NSCoder) {
super.init(coder: coder)
self.createNibView()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.createNibView()
}
private func createNibView() {
guard let view = loadFromNib() else { return }
self.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(view)
self.createConstraints(for: view)
self.setNeedsLayout()
}
private func loadFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as? UIView
view?.translatesAutoresizingMaskIntoConstraints = false
return view
}
private func createConstraints(for view: UIView) {
NSLayoutConstraint.activate([
self.topAnchor.constraint(equalTo: view.topAnchor),
self.bottomAnchor.constraint(equalTo: view.bottomAnchor),
self.leadingAnchor.constraint(equalTo: view.leadingAnchor),
self.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
}
and a subclass of this view:
import UIKit
#IBDesignable
class RDTextField: NibView {
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var errorLabel: UILabel!
#IBOutlet weak var errorIcon: UIImageView!
}
with the associated xib:
In storyboard, I have added a view for which I've set the class of this custom view, which is the selected view from the image with the storyboard.
I have also tried playing with awakeAfter(using coder:) with no better results, I tried to use the autoresizing mask, which is slightly better, in the sense that it sets the origin correctly, but it does not have the correct size. Anyway, I would like to use translatesAutoresizingMaskIntoConstraints disabled and use my own constraints.
I have also tried to debug these views, but XCode won't attach to any process and I cannot find the logs inside the Console.app.
Note: I have an IBDesignable extension for UIView which helps me setting border color, width and radius. I have tried to disable it, but without any results.
Note 2: At runtime everything works properly, but live view in storyboard is pretty much needed as well.
Did anyone encounter this or have any idea how to approach this issue?
The root view of a Storyboard object always uses an Autoresizing Mask. Your code is disabling that.
Remove one line from your createNibView() func in your NibView class
private func createNibView() {
guard let view = loadFromNib() else { return }
// don't do this!
//self.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(view)
self.createConstraints(for: view)
self.setNeedsLayout()
}
That should do it.

How to use custom view with in swift?

I want to add a custom view in TableViewHeader. But when I run the following code it creates a Cycle and app stuck for any user interaction.
import UIKit
class ExpandableView: UIView {
#IBOutlet weak var userImgView: UIImageView!
#IBOutlet weak var userNamelbl: UILabel!
#IBOutlet weak var countLbl: UILabel!
#IBOutlet weak var rightArrowImgView: UIImageView!
var isExpanded = false
var contentView: UIView!
#IBOutlet weak var checkMarkView: UIImageView!
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
public override init(frame: CGRect) {
super.init(frame: frame)
setUpView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpView()
}
private func setUpView() {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "ExpandableView", bundle: bundle)
self.contentView = nib.instantiate(withOwner: self, options: nil).first as? UIView
addSubview(contentView)
contentView.center = self.center
contentView.autoresizingMask = []
contentView.translatesAutoresizingMaskIntoConstraints = true
}
}
I am using it as follows:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let frame = CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 60)
let expandabelView = ExpandableView(frame: frame)
return expandabelView
}
And it shows following error on run.
There may be many other ways, but I recommend you to make your ExpandableView reusable to improve performance.
First of all, simplify your ExpandableView class:
import UIKit
class ExpandableView: UITableViewHeaderFooterView {
#IBOutlet weak var userImgView: UIImageView!
#IBOutlet weak var userNamelbl: UILabel!
#IBOutlet weak var countLbl: UILabel!
#IBOutlet weak var rightArrowImgView: UIImageView!
var isExpanded = false
#IBOutlet weak var checkMarkView: UIImageView!
}
Please do not miss that the superclass is UITableViewHeaderFooterView, not UIView.
Second, check the settings of your ExpandableView.xib:
The Custom View setting of the defined view needs to be ExpandableView.
When you cannot choose ExpandableView from the pull down list, you may need to input manually. Do not forget to check Inherit Module From Target.
The Custom View setting of the File's Owner needs to be empty.
If there's some class already set, remove it manually.
Confirm all Outlets are connected properly to your ExpandableView.
(You should better reconnect them all, after you modified your xib.)
You may need to re-structure your view hierarchy and/or constraints.
Third, modify your view controller holding the table view.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.delegate = self
tableView.dataSource = self
//...
let nib = UINib(nibName: "ExpandableView", bundle: nil)
tableView.register(nib, forHeaderFooterViewReuseIdentifier: "ExpandableView")
tableView.estimatedSectionHeaderHeight = 60
tableView.sectionHeaderHeight = UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let expandabelView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "ExpandableView")
// Frame size should be represented with constraints.
return expandabelView
}
When you create custom UIView, it should follow this if you want to use it init with frame.
class CustomView: UIView {
//This should be contentview of your xib
#IBOutlet var view: UIView!
let nibName = "CustomView"
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
private func xibSetup()
{
Bundle.main.loadNibNamed(nibName, owner: self, options: nil)
self.view.autoresizesSubviews = true
self.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.view)
self.view.frame = self.frame
}
}
Since you need to load view from nib, just load it and then add subview to your view. Then set content view's frame and set autoresizing mask of content view correctly
private func setUpView() {
Bundle.main.loadNibNamed("ExpandableView", owner: self, options: nil)
addSubview(contentView)
contentView.frame = bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}

Reusable .xib + cocoa class components, how to auto size and style superview in storyboard?

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.

Resources