view.frame is not set when IBDesignable loads xib - swift

I have a simple UIView subclass that I have made #IBDesignable.
I overloaded init frame and init coder. My code loads the view description from a .xib file.
After the .xib file has been loaded I try to set:
view.clipsToBounds = true
view.layer.cornerRadius = frame.size.height / 2
to get a "rectangle with circular sides".
The problem is that frame.size.height = 0 at that time (after .xib is loaded and my custom view drawn for the first time by Interface Builder).
Is there a workaround?
Here it is my code:
#IBDesignable
class CustomUIVIew: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
private var view: UIView!
private let nibName = "CustomUIView"
private func xibSetup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
addSubview(view)
setupView()
}
private func loadViewFromNib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
return view
}
private func setupView() {
view.clipsToBounds = true
view.layer.cornerRadius = frame.size.height / 2
}
}
Thank you, Luca-

You should move any size related adjustments into layoutSubviews. This way, not only will it be rendered correctly in IB, but also on device, if the size of the window changes, the cornerRadius will be adjusted accordingly.

prepareForInterfaceBuilder did the job
override func prepareForInterfaceBuilder() {
view.clipsToBounds = true
view.layer.cornerRadius = bounds.height / 2
}

Related

Custom view for NSMenuItem appearing blank

I've been trying to make a custom view for a NSMenuItem with an image on it. The code below overrides the original view, but without the image. The item appears blank and smaller.
This code is only an example!
class CustomItemView: NSView {
private let imageView: NSImageView = NSImageView()
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
// self.imageView.frame = frameRect
self.imageView.image = NSImage.init(named: "example")!
self.addSubview(self.imageView)
self.imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.imageView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
self.imageView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
self.imageView.widthAnchor.constraint(equalToConstant: 100),
self.imageView.heightAnchor.constraint(equalToConstant: 100)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I then simply add set the view like this
let newItem: NSMenuItem = NSMenuItem()
newItem.view = CustomItemView()
newItem.target = self
self.addItem(newItem)

How to create this view with constraints programtically

I feel this is pretty simple to accomplish but I can't seem to figure it out. I'm fairly new to not using the storyboard and trying to learn how to set my constraints programatically for my views. I created the view that I want easily in storyboard but can't seem to get it programatically.
I have my view controller has my parent view, and then I call a container view. I imagine in the container view is where I setup my constraints but I can't get the height of my view to stay the same every-time I change to a different device
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var clariView = ClariContainerView()
view.addSubview(clariView)
}
}
This my view controller and then my ClariContainerView looks like this:
class ClariContainerView: UIView {
lazy var clariQuestionView: UIView = {
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 0))
containerView.backgroundColor = .blue
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
public func setupView() {
addSubview(clariQuestionView)
setupLayout()
}
public func setupLayout() {
NSLayoutConstraint.activate([
clariQuestionView.heightAnchor.constraint(equalToConstant: 169)
])
}
}
What I'm trying to recreate is this:
I need the height of the blue view to always be 169.
Here is how you would do that:
First, you don't need to define a frame for your containerView since the translatesAutoresizingMaskIntoConstraints = falsestatement is specifying that you'll be using auto-layout and therefore the frame will be ignored:
lazy var clariQuestionView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .blue
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
And here is how you would define your constraints. You need to set height, but also need to pin the view to the bottom, the leading, and the trailing edges of self.view:
public func setupLayout() {
NSLayoutConstraint.activate([
clariQuestionView.heightAnchor.constraint(equalToConstant: 169),
clariQuestionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
clariQuestionView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
clariQuestionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor)
])
}
For such a basic layout you don't really need to add heightAnchor. Here is a simple way to achieve desired behavior + bonus β€” a code snippet to adjust height according to the device's safeAreaInsets.
class ClariContainerView: UIView {
lazy var clariQuestionView: UIView = {
let desiredContainerHeigh = 169
// If you want, you can use commented code to adjust height according to the device's safe area.
// This might be needed if you want to keep the same height over safe area on all devices.
// let safeAreaAdjustment = UIApplication.shared.keyWindow?.rootViewController?.view.safeAreaInsets.bottom ?? 0
let containerView = UIView(frame: CGRect(x: 0, y: UIScreen.main.bounds.height - 169, width: UIScreen.main.bounds.width, height: 169))
containerView.backgroundColor = .blue
containerView.translatesAutoresizingMaskIntoConstraints = true
return containerView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
public func setupView() {
addSubview(clariQuestionView)
}
}

UIView is not rounded

I have a small problem and not sure why it is not working. I have a code:
func rounded(cornerRadius: CGFloat? = nil) {
if let cornerRadius = cornerRadius {
layer.cornerRadius = cornerRadius
} else {
layer.cornerRadius = min(frame.size.height, frame.size.width) / 2
}
clipsToBounds = true
}
And I am trying to use like this:
cameraImageContainer.rounded()
cameraImageContainer = UIView
The image is not really very rounded and it doesn't look good.
Any Suggestions please?
Since the frame can change, it's easier to implement the corner radius when the bounds are set. Give this a try:
class CircleView: UIView {
override var bounds: CGRect {
didSet {
self.layer.cornerRadius = min(bounds.size.height, bounds.size.width) / 2
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.commonInit()
}
private func commonInit() {
clipsToBounds = true
}
}
Call the function to round the image in viewDidApper like this
override func viewDidAppear(_ animated: Bool) {
cameraImageContainer.rounded()
}
because the frames of the button will be set only after the view is loaded
if you waant to call it in viewDidLoad create a custom class for imageView and set it in the storybaord

Custom view's IBOutlet is nil

So I have a main viewController that has a stack view to which I want to add multiple instances of custom views I have created.
So my thought process was to create a custom view that inherits from UIView. I want the view to always be 40x40 so I created a new init to take care of this? (not sure if this is correct):
class QuickAddView: UIView {
#IBOutlet weak var iconLabel: UILabel!
var task: Task = Task()
public init(task: Task) {
self.task = task
super.init(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
configureView()
}
private func configureView() {
self.layer.cornerRadius = self.frame.size.width / 2
self.backgroundColor = task.color
configureViewElements()
}
private func configureViewElements() {
configureIconLabel()
}
private func configureIconLabel() {
// CRASH: - iconLabel is nil here
self.iconLabel.text = task.icon
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
I then have a QuickAddView nib that sets its custom class to QuickAddView.swift
Lastly, I create the custom views in my viewController:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
configureViewElements()
}
private func configureViewElements() {
configureQuickAddStackView()
}
private func configureQuickAddStackView() {
let quickAddView = QuickAddView(task: Task(name: "Go to the store", icon: "🍐", color: .purple))
quickAddStackView.addArrangedSubview(quickAddView)
}
The problem I'm having is that my iconLabel is nil when I try to set up my QuickAddView. I also don't know if I'm doing this process of creating a custom view correct.
Since you are connecting the IBOutlet from xibFile you have to use
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
configureView()
}
Also in your class you have to instantiate it with
if let quickAddView = Bundle.mainBundle().loadNibNamed("QuickAddView", owner: self, options: nil).first as? QuickAddView {
quickAddView.task = task
}
The problem is that you're creating a custom view in code, but using an IBOutlet for the iconLabel. An IBOutlet is for something you've built in Interface Builder, not in code. If you want to create the QuickAddView in code, you need to also create the iconLabel in code, in the QuickAddView class. You need to make these modifications:
weak var iconLabel: UILabel?
private func configureIconLabel() {
iconLabel = UILabel(frame: CGRect())
if let iconLabel = iconLabel {
iconLabel.text = task.icon
addSubview(iconLabel)
}
}
Note that I passed in a zeroed out CGRect, but you'll want to use whatever you want the UILabels origin and size to be, or use autolayout to configure where the iconLabel is displayed.

UIView from NIB not conforming to parent constraints after adding autolayout constraints

I am loading my NIB file to my view using :
override init(frame: CGRect) {
super.init(frame: frame)
Bundle.main.loadNibNamed("EventPopup", owner: self, options: nil)
//self.frame = self.view.bounds - DIDN'T WORK
//self.frame.size.width = 300 - DID'T WORK
self.addSubview(self.view); // adding the top level view to the view hierarchy
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
Bundle.main.loadNibNamed("EventPopup", owner: self, options: nil)
self.addSubview(self.view); // adding the top level view to the view hierarchy
}
This works and adds my NIB but the width of the UIView stretches outside of the parent container. This only occurs after adding autolayout to the uiview. I hav checked and the initial frame is size 600x600.
Even after trying to reset the NIBs size by calling layoutSubviews() and putting the new frame constraints in it still refuses to resize. For example:
override func layoutSubviews() {
// self.frame.size.width = 300
//self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: 200, height: self.frame.height)
}
Take a look at this post here. Look at the 3rd tip which shows how to create a view that loads from a nib. The code is Reproduced below (disclaimer it is written in Swift 2):
#IBDesignable
class ProfileView: UIView {
#IBOutlet var imgView: UIImageView!
#IBOutlet var labelOne: UILabel!
#IBOutlet var labelTwo: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
setUp()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUp()
}
func setUp() {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: β€œProfile”, bundle: bundle)
let viewFromNib = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
addSubview(viewFromNib)
viewFromNib.translatesAutoresizingMaskIntoConstraints = false
self.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
β€œH:|[v]|”,
options: NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: [β€œv”:viewFromNib]
)
)
self.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
β€œV:|[v]|”,
options: NSLayoutFormatOptions(rawValue: 0),
metrics: nil, views: [β€œv”:viewFromNib]
)
)
}
}
Once you have it set up properly in code and the nib, you should be able to initialize it easily. Either in Interface builder or in code:
// ViewController
override viewDidLoad() {
super.viewDidLoad()
self.addSubview(ProfileView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)))
}