Swift - UIView when done updating its frame? - swift

I have a UITableViewCell in which I add my custom view, say
class MyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
convenience required init(size: CGFloat) {
self.init(frame: CGRect.zero)
height = size
// init my other sub contents [1]
}
}
From my UITableViewCell:
let myView = MyView(size: 35)
contentView.addSubView(myView)
My problem: my other UI components in MyView depend on the width and height of MyView and it's too early to set their position in [1] because MyView's frame is now (x: 0, y: 0, width: 0, height: height)
Where should I put my other UI components in MyView? Is there a delegate in UIView that tells me: "Hey, the frame of the view is updated with its actual size."? (like viewDidAppear in a UIViewController)
Regards,

You can do
override var frame: NSRect {
didSet {
//update your subviews
}
}
In your MyView class. But really, you should just look into setting up proper constraints:
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/

Related

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

An additional view overlay the main view

I have a main view where there are buttons and google map.
let maps = MapView(frame: view.bounds)
mapview = maps
view.addSubview(mapview!)
And by changing the state of the user, details are added to the main view in the same way.
let new = ButtonView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height))
ButView = new
view.addSubview(ButView!)
When adding these views, my map is overlapped by layers of additional views and I cannot do any actions. Because the extra view all overlaps, how to avoid it?
class ButtonView: UIView {
var FromButton : MainButton = MainButton()
var toButton : MainButton = MainButton()
var Send : MainButton = MainButton()
public override init(frame: CGRect) {
super.init(frame: frame)
addSubview(Send)
addSubview(FromButton)
addSubview(toButton)
addview()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Getting size of UIView in init

I have made a custom subclass of UIView in my iOS application, and I am trying to get the computed sizes of the view in the view's init method, so I can use them when creating subviews to put inside the custom view.
The custom view is inside a stack view, which assigns my view 1/3 of the total (main view) height.
My init looks like this:
var mySubView: UIImageView
required init?(coder aDecoder: NSCoder) {
mySubView = UIImageView()
super.init(coder: aDecoder)
let viewWidth = Int(self.frame.size.width)
let viewHeight = Int(self.frame.size.height)
mySubView.frame = CGRect(x: 0, y: 0, width: viewWidth, height: viewHeight)
mySubView.backgroundColor = UIColor.cyan
self.addSubview(mySubView)
}
However, the heights and widths are not reported correctly. For instance, mySubView above only ends up filling about half of the total space of the custom view.
Any help would be greatly appreciated!
The initializer is called too early in the lifecycle of the view to accurately do layout unless you know the exact dimensions in advance. Even so, it is idiomatically the wrong place to do it.
Try using the layoutSubviews method as such:
class SubView: UIImageView {
var mySubView: UIImageView
required init?(coder aDecoder: NSCoder) {
mySubView = UIImageView()
mySubView.backgroundColor = UIColor.cyan
super.init(coder: aDecoder)
self.addSubview(mySubView)
}
override func layoutSubviews() {
mySubView.frame = self.bounds
super.layoutSubviews()
}
}
Now the subview bounds will be set properly at the start of each layout pass. It’s a cheap operation.
Also, the bounds property of a UIView is the frame translated to the view’s internal coordinate space. This means that normally this is true: bounds = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height). I suggest reading the documentation on view layout.
Alternatively, you can ditch manual layout entirely and use AutoLayout to do this for you.
class SubView: UIImageView {
var mySubView: UIImageView
required init?(coder aDecoder: NSCoder) {
mySubView = UIImageView()
mySubView.backgroundColor = UIColor.cyan
super.init(coder: aDecoder)
self.addSubview(mySubView)
mySubView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
mySubView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
mySubView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
mySubView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true
}
}

How do you subclass UIView and add another UIView inside of it with the same frame

Hi I am trying to create a custom loading bar view by subclassing UIView. I want to create one UIView with a fixed frame, and another UIView that is inside of it. When I initialize the inner UIView, with the frame passed in by this method override init(frame: CGRect), the two views have different origins. I want the two views to be directly on top of each other to start out. I also want to be able to update the innerBar by calling this uploadBar.setLoadingPercentage(percent: 53.5)
Here is the code:
Creating the UploadBar
let uploadBar = UploadBar(frame: CGRect(x: 40, y: 40, width: 400, height: 40))
view.addSubview(uploadBar)
Subclassing UploadBar
import UIKit
class UploadBar: UIView {
var innerBar: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
innerBar = UIView(frame: frame)
innerBar.backgroundColor = UI.customBlue()
addSubview(innerBar)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setLoadingPercentage(percent: Double) {
// change innerBar's frame and redraw
}
}
For your inner view, you only require the width and height from the parent rect. The x and y should be zero relative to the parent view:
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
let innerRect = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
innerBar = UIView(frame: innerRect)
innerBar.backgroundColor = UI.customBlue()
addSubview(innerBar)
}

Subclassing UIView in swift

When subclassing UIView, how do you access the parent classes methods and properties?... this is not working:
//
// Draw2D.swift
// Draw2D
//
import UIKit
class Draw2D: UIView {
let coloredSquare = Draw2D()
coloredSquare.backgroundColor = UIColor.blueColor()
coloredSquare.frame = CGRect(x: 0, y: 120, width: 50, height: 50)
addSubview(coloredSquare)
}
Thanks
You did not create an initialiser for the your Draw2D class. It needs this to be able to call super.init, this in turn actually creates the UIView stuff from which you are subclassing.
You also created another instance of Draw2D in your class. This is bad, if you actually do this in an initialiser (where that code belongs) it will create an infinite amount of subviews.
Recursive functions are super awesome, recursive initialiser are very bad ;)
import UIKit
class Draw2D: UIView {
// this will create an infinite amount of coloredSquare's => it is a recursive initialiser
let coloredSquare : Draw2D
override init(frame: CGRect) {
coloredSquare = Draw2D(frame: frame)
super.init(frame: frame)
self.frame = frame
coloredSquare.backgroundColor = UIColor.blueColor()
coloredSquare.frame = CGRect(x: 0, y: 120, width: 50, height: 50)
addSubview(coloredSquare)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
After calling super.init() you can call stuff from the super class. Use self for extra clarity, but this is not needed.
class Draw2DCorrected: UIView {
init() {
let rect = CGRect(x: 0, y: 120, width: 50, height: 50)
super.init(frame: rect)
self.frame = rect // inherited stuff from super class -> UIView
self.backgroundColor = UIColor.blueColor() // inherited stuff from super class -> UIView
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
var coloredSquare = Draw2DCorrected() // playground only