Why doesn't Autolayout change a views frame - swift

I created a view in a func of its superview
let black = NSView(frame: NSRect(x: 0, y: 0, width: 10, height: 10))
black.translatesAutoresizingMaskIntoConstraints = false
black.wantsLayer = true
black.layer?.backgroundColor = NSColor.black.cgColor
self.addSubview(black)
black.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -15).isActive = true
black.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 15).isActive = true
black.heightAnchor.constraint(equalToConstant: 115).isActive = true
black.widthAnchor.constraint(equalToConstant: 115).isActive = true
print ("origin (\(black.frame.origin.x),\(black.frame.origin.y)) size (\(black.frame.size.width),\(black.frame.size.height)) ")
The output is origin(0,0), size(10,10), these are the values the view was created with.
On screen the black view is positioned as expected origin:(15,15) size:(115,115).
Why is the frame not updated?

After setting the constraints you can update the frame of your view calling the same of layoutIfNeeded function for Cocoa:
black.layoutSubtreeIfNeeded()

Related

UIScrollView does not scroll + second scrollView shows nothing

it is unpleasant for me but I need some help with my UIScrollViews. They are both arranged subviews of a stackView on my MainVC.
The weird thing is that only one of them is showing content, although I used the same code for both scrollViews. The second problem is that they do not scroll, here is my code:
class HomeVC: UIViewController, UIScrollViewDelegate {
var views = [UIImageView]()
//StackView
let stackView = UIStackView()
let topView = UIScrollView()
let bottomView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = MyColors.soft_pink
prepare_data()
print(views.count)
}
//MARK: - GUI
func setUpStackView() {
view.addSubview(stackView)
stackView.alignment = .center
stackView.axis = .vertical
stackView.distribution = .equalCentering
stackView.spacing = 5
stackView.addArrangedSubview(topView)
stackView.addArrangedSubview(bottomView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -25).isActive = true
setUpTopView()
setUpBottomView()
}
func setUpTopView() {
topView.delegate = self
topView.layer.cornerRadius = 25
topView.layer.masksToBounds = true
topView.layer.borderWidth = 10
topView.layer.borderColor = UIColor.white.cgColor
topView.contentMode = .scaleAspectFit
topView.showsHorizontalScrollIndicator = false
topView.isPagingEnabled = true
topView.contentSize = CGSize(width: topView.frame.width * CGFloat(views.count),height: topView.frame.height)
for i in 0..<views.count {
topView.addSubview(views[i])
views[i].frame = CGRect(x: topView.frame.width * CGFloat(i), y: 0, width: 350, height: 250)
views[i].layer.cornerRadius = 25
}
topView.translatesAutoresizingMaskIntoConstraints = false
topView.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 10).isActive = true
topView.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: -10).isActive = true
topView.heightAnchor.constraint(equalToConstant: 250).isActive = true
}
func setUpBottomView() {
bottomView.delegate = self
bottomView.layer.cornerRadius = 25
bottomView.layer.masksToBounds = true
bottomView.layer.borderWidth = 10
bottomView.layer.borderColor = UIColor.white.cgColor
bottomView.contentMode = .scaleAspectFit
bottomView.showsHorizontalScrollIndicator = false
bottomView.isPagingEnabled = true
bottomView.contentSize = CGSize(width: bottomView.frame.width * CGFloat(views.count),height: bottomView.frame.height)
for i in 0..<views.count {
bottomView.addSubview(views[i])
views[i].frame = CGRect(x: bottomView.frame.width * CGFloat(i), y: 0, width: 350, height: 250)
views[i].layer.cornerRadius = 25
}
bottomView.translatesAutoresizingMaskIntoConstraints = false
bottomView.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 10).isActive = true
bottomView.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: -10).isActive = true
bottomView.heightAnchor.constraint(equalToConstant: 250).isActive = true
}
func prepare_data() {
for x in 1...6 {
let woman = UIImage(named: "woman\(x)")
let womanView = UIImageView(image: woman)
womanView.contentMode = .scaleAspectFill
views.append(womanView)
}
setUpStackView()
}
}
Could someone please be so kind and tell me what I have wrong? Thank you in advance!
Try to debug by printing values. bottomView.frame.width was zero at initialisation, so update subviews in viewDidLayoutSubviews. There are more ways you can look for frame update detection.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
DispatchQueue.main.async {
self.updateSubviewFrames()
}
}
func updateSubviewFrames() {
print(bottomView.frame)
bottomView.contentSize = CGSize(width: bottomView.frame.width * CGFloat(views.count),height: bottomView.frame.height)
for i in 0..<views.count {
views[i].frame = CGRect(x: bottomView.frame.width * CGFloat(i), y: 0, width: 350, height: 250)
views[i].layer.cornerRadius = 25
}
}
You've done a few things wrong...
First, because it's easy -- the reason you don't see anything in your Top scroll view is because you add your image views (from the views array) to topView, and then you add them to bottomView which removes them from topView!
So, you need one array of views for topView and an array of other views for bottomView.
Next, you are using auto-layout / constraints to size and position your stack view, then trying to use the frames of the stack view's arranged subviews -- for example:
bottomView.contentSize = CGSize(width: bottomView.frame.width * CGFloat(views.count),height: bottomView.frame.height)
but, that's all being done in functions called from viewDidLoad() when auto-layout has not yet configured the view frames.
You're also adding your scroll view's as arranged subviews of the stack view, but then constraining them to the stack view (which is not the way to do it):
topView.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 10).isActive = true
As a side note: the easiest way to manage a paged scroll view is to embed the "pages" (your image views) in a horizontal stack view, setting the width of each view to the width of the scroll view's Frame Layout Guide (minus desired spacing).
Here's a modified version of your code to take a look at:
class HomeVC: UIViewController, UIScrollViewDelegate {
var topViews = [UIImageView]()
var botViews = [UIImageView]()
//StackView
let stackView = UIStackView()
let topView = UIScrollView()
let bottomView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemPink // MyColors.soft_pink
prepare_data()
setUpStackView()
setUpTopAndBottomViews()
}
func prepare_data() {
// create 6 image views
// for BOTH Top and Bottom scroll views
// I'll assume you have "woman" and "man" images
for x in 1...6 {
let woman = UIImage(named: "woman\(x)")
let man = UIImage(named: "man\(x)")
let womanView = UIImageView(image: woman)
womanView.contentMode = .scaleAspectFill
topViews.append(womanView)
let manView = UIImageView(image: man)
manView.contentMode = .scaleAspectFill
botViews.append(manView)
}
}
func setUpStackView() {
// setup stack view
view.addSubview(stackView)
// .alignment should be .fill, not .center
//stackView.alignment = .center
stackView.alignment = .fill
stackView.axis = .vertical
// let's use .fillEqually instead of .equalCentering
//stackView.distribution = .equalCentering
stackView.distribution = .fillEqually
stackView.spacing = 5
stackView.addArrangedSubview(topView)
stackView.addArrangedSubview(bottomView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -25).isActive = true
}
func setUpTopAndBottomViews() {
// setup both scroll views with the same properties
[topView, bottomView].forEach { v in
v.delegate = self
v.layer.cornerRadius = 25
v.layer.masksToBounds = true
v.layer.borderWidth = 10
v.layer.borderColor = UIColor.white.cgColor
v.showsHorizontalScrollIndicator = false
v.isPagingEnabled = true
}
// let's use auto-layout here
// if you want horizontal paged scrolling, easiest route is to
// use a horizontal stack view
// create a stack view
let topStack = UIStackView()
topStack.translatesAutoresizingMaskIntoConstraints = false
topStack.spacing = 10
// add stack view to topView
topView.addSubview(topStack)
for i in 0..<topViews.count {
topStack.addArrangedSubview(topViews[i])
topViews[i].layer.cornerRadius = 25
// set view width and height equal to
// topView's Frame Layout Guide
// allowing for 5-pts "padding" on the sides
topViews[i].widthAnchor.constraint(equalTo: topView.frameLayoutGuide.widthAnchor, constant: -10.0).isActive = true
topViews[i].heightAnchor.constraint(equalTo: topView.frameLayoutGuide.heightAnchor).isActive = true
}
// now we'll set constraints on the stack view to
// topView's Content Layout Guide
NSLayoutConstraint.activate([
topStack.topAnchor.constraint(equalTo: topView.contentLayoutGuide.topAnchor),
topStack.leadingAnchor.constraint(equalTo: topView.contentLayoutGuide.leadingAnchor, constant: 5.0),
topStack.trailingAnchor.constraint(equalTo: topView.contentLayoutGuide.trailingAnchor, constant: -5.0),
topStack.bottomAnchor.constraint(equalTo: topView.contentLayoutGuide.bottomAnchor),
])
// same thing with the bottom scroll view
// create a new stack view
let botStack = UIStackView()
botStack.translatesAutoresizingMaskIntoConstraints = false
botStack.spacing = 10
// add stack view to bottomView
bottomView.addSubview(botStack)
for i in 0..<botViews.count {
botStack.addArrangedSubview(botViews[i])
botViews[i].layer.cornerRadius = 25
// set view width and height equal to
// bottomView's Frame Layout Guide
// allowing for 5-pts "padding" on the sides
botViews[i].widthAnchor.constraint(equalTo: bottomView.frameLayoutGuide.widthAnchor, constant: -10.0).isActive = true
botViews[i].heightAnchor.constraint(equalTo: bottomView.frameLayoutGuide.heightAnchor).isActive = true
}
// now we'll set constraints on the stack view to
// bottomView's Content Layout Guide
NSLayoutConstraint.activate([
botStack.topAnchor.constraint(equalTo: bottomView.contentLayoutGuide.topAnchor),
botStack.leadingAnchor.constraint(equalTo: bottomView.contentLayoutGuide.leadingAnchor, constant: 5.0),
botStack.trailingAnchor.constraint(equalTo: bottomView.contentLayoutGuide.trailingAnchor, constant: -5.0),
botStack.bottomAnchor.constraint(equalTo: bottomView.contentLayoutGuide.bottomAnchor),
])
}
}

Why does getting my view's frame return the full screen, even tho the view has been run through the safeArea constraints?

Before presenting my GameScene I apply the safeAreaLayoutGuide to the initial view screen (code below is from GameViewController).
If at any point before I call view.presentScene(scene) I try to get the view's frame, I get back an empty set.
If however I get the frame, after presenting the scene, I get the rect for the full screen, even though the view does appear within the safeAreaLayoutGuide.
How can I get the view's safe adjusted screen coordinates? I should note I am doing this so I can apply the proper constraints to my GameScene, as I can only find how to do it for UIView.
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView?{
let screenWidthInPoints = UIScreen.main.bounds.width
let screenHeightInPoints = UIScreen.main.bounds.height
let imageView = UIView()
imageView.backgroundColor = .red
view.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
imageView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor , constant: -0).isActive = true
let scene = GameScene(size: CGSize(width: screenWidthInPoints,
height: screenHeightInPoints))
scene.backgroundColor = .black
scene.scaleMode = .aspectFit
view.presentScene(scene)
var myRect = view.frame
UIScreen.main.bounds represents the entire screen, not just the safe area. You will have to create a separate view constrained to safe area insets to get the proper safe area size. In your case, you already have an imageView placed within the safe area. Try printing out its size after adding the constraints and do it within a DispatchQueue.main.async to make sure that it gets called only after the current pass layout is complete.
let imageView = UIView()
imageView.backgroundColor = .red
view.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
imageView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor , constant: -0).isActive = true
DispatchQueue.main.async {
print("imageView.frame: ", imageView.frame)
let scene = GameScene(size: CGSize(width: imageView.frame.width,
height: imageView.frame.height))
scene.backgroundColor = .black
scene.scaleMode = .aspectFit
view.presentScene(scene)
}

Why UITextView won't appear?

I have a custom tableView Cell, I can't figure out why my UITextView won't show up.
This is my UITextView:
fileprivate let notificationBody: UITextView = {
let text = UITextView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
text.translatesAutoresizingMaskIntoConstraints = false
text.backgroundColor = UIColor.appWhite
text.font = UIFont.preferredFont(forTextStyle: .headline)
text.isScrollEnabled = true
text.isEditable = false
text.text = "hjfhsdjfdsfhjksdfhksdjhkfsdjhfshjdfahsdjfdsjklfjsdkfjksdlfjkdsfjklsdlkjfsdjklfsdjklfsdjklfjkldsfjklsdfkjldskjfsdlkjfsdklfjlkdsjflksdajflkdsjflksdjflksdajflksdajflsdkjflsdkjflsdkjflsdkjflsdjflsdjflsdjfsdlkjfslkd"
text.textAlignment = NSTextAlignment.natural
return text
}()
this is how I set it:
fileprivate func setupNotificationBody(){
addSubview(notificationBody)
NSLayoutConstraint.activate([
notificationBody.topAnchor.constraint(equalTo: notificationTitle.bottomAnchor, constant: 5),
notificationBody.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 5),
notificationBody.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -5),
notificationBody.bottomAnchor.constraint(equalTo: self.dismissNotificationButton.topAnchor, constant: -5)
])
}
And ofcourse I called setupNotificationBody in Init.
I also have in this cell another button and a uilabel which show up correctly.
It looks like you need your cell to have dynamic height. In this case, you should set the property isScrollEnabled in your UITextView to false, so the UITextView will be able to calculate it's own height.
Also, remember to set the cell height to UITableView.automaticDimension in your UITableViewDelegate.

Scroll view not scrolling horizontal

I have a custom view, i set it in parent like:
func setup(){
view.backgroundColor = .gray
view.addSubview(chartView)
chartView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
chartView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
chartView.topAnchor.constraint(equalTo: view.topAnchor, constant: statusAndNavigationBarHeight).isActive = true
chartView.heightAnchor.constraint(equalToConstant: Dimensions.chartHeight.value).isActive = true
}
Then in that view i tried to set up a scroll:
scroll = UIScrollView.init(frame: CGRect(x: 0.0, y: 0.0, width: scrollWidth(), // print 728.0
height: Double(Dimensions.chartHeight.value))) // print 400.0
scroll.isScrollEnabled = true
scroll.showsHorizontalScrollIndicator = true
addSubview(scroll)
}
And thats all, when i launch app i can't drag and scroll horizontally, in debug editor i can't see that it is scroll view here lying with large width.
The scrollView doesn't scroll with it's size , it needs a content that define it's content size for example
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let chartView = UIView()
chartView.translatesAutoresizingMaskIntoConstraints = false
chartView.backgroundColor = .red
view.backgroundColor = .gray
view.addSubview(chartView)
chartView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
chartView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
chartView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
chartView.heightAnchor.constraint(equalToConstant:200).isActive = true
let scroll = UIScrollView(frame: CGRect(x: 0.0,
y: 0.0,
width: UIScreen.main.bounds.size.width, // print 728.0
height: 200.0))
scroll.isScrollEnabled = true
scroll.showsHorizontalScrollIndicator = true
chartView.addSubview(scroll)
let www = UIView()
www.backgroundColor = .green
www.translatesAutoresizingMaskIntoConstraints = false
scroll.addSubview(www)
www.leftAnchor.constraint(equalTo: scroll.leftAnchor).isActive = true
www.rightAnchor.constraint(equalTo: scroll.rightAnchor).isActive = true
www.topAnchor.constraint(equalTo: scroll.topAnchor).isActive = true
www.bottomAnchor.constraint(equalTo: scroll.bottomAnchor).isActive = true
www.heightAnchor.constraint(equalToConstant:200).isActive = true
www.widthAnchor.constraint(equalTo: view.widthAnchor,multiplier:2.0).isActive = true
scroll.addSubview(www)
}
}

parent UIView is not showing but the subviews are showing i nswift

this is the code I wrote
let topViewa = UIView()
topViewa.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(topViewa)
topViewa.backgroundColor = .white
topViewa.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 300).isActive = true
topViewa.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true
topViewa.frame.size = CGSize(width: screenWidth, height: 44)
let fw = UILabel(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
fw.backgroundColor = .red
topViewa.addSubview(fw)
with screenWidth being the width of the screen.
and when I run this, this is what I get
Why am I not getting the parent UIView with white background?
Why am I not getting the parent UIView with white background?
Because the white view has no size.
The line
topViewa.frame.size = CGSize(width: screenWidth, height: 44)
...has no effect. You have elected to use constraints to position and size this view. Now you must fulfill that contract, giving it both position and size through constraints alone. You have not provided any height or width constraints (or alternatively, bottom or trailing constraints), so the view has zero size and you see nothing.
The red subview, meanwhile, remains visible, because the white superview's clipsToBounds is false. If it were true, you wouldn't see the red subview either.