I am getting one weird issue, I am setting constraints on the view using storyboard as below:
But if I am accessing all applied constraint by code then always getting zero (0).
for self.view it's returning the constraint but for yellow view getting zero.
Use this code (Swift 4+ and Xcode 9.4.1)
import UIKit
class ViewController: UIViewController {
#IBOutlet var subView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
print(self.view.getAllConstraints().count)
print(self.subView.getAllConstraints().count)
}
}
extension UIView {
func getAllConstraints() -> [NSLayoutConstraint] {
var views = [self]
var view = self
while let superview = view.superview {
views.append(superview)
view = superview
}
return views.flatMap({ $0.constraints }).filter { constraint in
return constraint.firstItem as? UIView == self ||
constraint.secondItem as? UIView == self
}
}
}
Just try to print constraints in viewDidAppear. In viewDidLoad method constraints not loaded for view so, just try in viewDidAppear method.
for your information when viewDidLoad is called subviews is not laid out yet thats why you getting count zero put you code into viewDidLayoutSubviews you will get your constraints there
Related
I notice that, if I perform add/ expand animation within an UIScrollView, it will cause unwanted scrolling behavior, when the UIScrollView fill with enough content to become scroll-able.
As you can see in the following animation, initially, the add/ expand animation works just fine.
When we have added enough item till the UIScrollView scrollable, whenever a new item is added, and UIScrollView will first perform scroll down, and then scroll up again!
My expectation is that, the UIScrollView should remain static, when add/ expand animation is performed.
Here's the code which performs add/ expand animation.
Add/ expand animation
#IBAction func add(_ sender: Any) {
let customView = CustomView.instanceFromNib()
customView.hide()
stackView.addArrangedSubview(customView)
// Clear off horizontal swipe in animation caused by addArrangedSubview
stackView.superview?.layoutIfNeeded()
customView.show()
// Perform expand animation.
UIView.animate(withDuration: 1) {
self.stackView.superview?.layoutIfNeeded()
}
}
Here's the constraint setup of the UIScrollView & added custom view item
Constraint setup
Custom view
class CustomView: UIView {
private var zeroHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var borderView: UIView!
#IBOutlet weak var stackView: UIStackView!
override func awakeFromNib() {
super.awakeFromNib()
borderView.layer.cornerRadius = stackView.frame.height / 2
borderView.layer.masksToBounds = true
borderView.layer.borderWidth = 1
zeroHeightConstraint = self.safeAreaLayoutGuide.heightAnchor.constraint(equalToConstant: 0)
zeroHeightConstraint.isActive = false
}
func hide() {
zeroHeightConstraint.isActive = true
}
func show() {
zeroHeightConstraint.isActive = false
}
}
Here's the complete source code
https://github.com/yccheok/add-expand-animation-in-scroll-view
Do you have any idea why such problem occur, and we can fix such? Thanks.
Because of the way stack views arrange their subviews, animation can be problematic.
One approach that you may find works better is to embed the stack view in a "container" view.
That way, you can use the .isHidden property when adding an arranged subview, and allow the animation to update the "container" view:
The "add view" function now becomes (I added a Bool so we can skip the animation on the initial add in viewDidLoad()):
func addCustomView(_ animated: Bool) {
let customView = CustomView.instanceFromNib()
stackView.addArrangedSubview(customView)
customView.isHidden = true
if animated {
DispatchQueue.main.async {
UIView.animate(withDuration: 1) {
customView.isHidden = false
}
}
} else {
customView.isHidden = false
}
}
And we can get rid of all of the hide() / show() and zeroHeightConstraint in the custom view class:
class CustomView: UIView {
#IBOutlet weak var borderView: UIView!
#IBOutlet weak var stackView: UIStackView!
override func awakeFromNib() {
super.awakeFromNib()
borderView.layer.masksToBounds = true
borderView.layer.borderWidth = 1
}
override func layoutSubviews() {
super.layoutSubviews()
borderView.layer.cornerRadius = borderView.bounds.height * 0.5
}
}
Since it's a bit difficult to clearly show everything here, I forked your project with the changes: https://github.com/DonMag/add-expand-animation-in-scroll-view
Edit
Another "quirk" of animating a stack view shows up when adding the first arranged subview (also, when removing the last one).
One way to get around that is to add an empty view as the first subview.
So, for this example, in viewDidLoad() before adding an instance of CustomView:
let v = UIView()
stackView.addArrangedSubview(v)
This will make the first arranged subview a zero-height view (so it won't be visible).
Then, if you're implementing removing custom views, just make sure you don't remove that first, empty view.
If your stack view has .spacing = 0 noting else is needed.
If your stack view has a non-zero spacing, add another line:
let v = UIView()
stackView.addArrangedSubview(v)
stackView.setCustomSpacing(0, after: v)
I did a little research on this and the consensus was to update the isHidden and alpha properties when inserting a view with animations.
In CustomView:
func hide() {
alpha = 0.0
isHidden = true
zeroHeightConstraint.isActive = true
}
func show() {
alpha = 1.0
isHidden = false
zeroHeightConstraint.isActive = false
}
In your view controller:
#IBAction func add(_ sender: Any) {
let customView = CustomView.instanceFromNib()
customView.hide()
stackView.addArrangedSubview(customView)
self.stackView.layoutIfNeeded()
UIView.animate(withDuration: 00.5) {
customView.show()
self.stackView.layoutIfNeeded()
}
}
Also, the constraints in your storyboard aren't totally correct. You are seeing a red constraint error because autolayout doesn't know the height of your stackView. You can give it a fake height and make sure that "Remove at build time" is checked.
Also, get rid of your scrollView contentView height constraint defined as View.height >= Frame Layout Guide.height. Autolayout doesn't need to know the height, it just needs to know how subviews inside of the contentView stack up to define its vertical content size.
Everything else looks pretty good.
I want to update the bottom anchor of my textview to a constant equal to the height of the keyboard when it appears so that it doesn't cover the text in the textView. I have a constraint identifier in Main.storyboard set as "bottomTextViewConstraint" for my textView, we well as the following code:
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
print("This prints")
for constraint in self.textView.constraints {
print("This does not print")
if constraint.identifier == "bottomTextViewConstraint" {
constraint.constant = keyboardSize.height
print("This does not print")
}
}
textView.updateConstraints()
}
}
}
self.textView.constraints is nil... It seems that programatically I can't access what I have set up in the storyboard. Any ideas why?
A constraint relating the bottom anchor of a text view and its super view's bottom anchor will be added to the text view's super view, so you cannot find it in textView.constraints.
A much simpler way to do this is to add an IBOutlet from the storyboard:
#IBOutlet var bottomConstraint: NSLayoutConstraint!
Right click on your view controller in the storyboard, then connect bottomConstraint to the constraint shown in the outline view:
Then you can remove your entire for loop and replace it with just:
bottomConstraint.constant = keyboardSize.height
I have a UIViewScroll(background color is blue) in view controller. I need a UIView(background color is white) that were from Xib. The Xib view has a UILabel(background color is green) with constraints. Now, the problem is UILabel constraints not applied after adding it to scrollView. How to add UIView without loss of constraints? Refer following screenshots and code.
Note:
I need to just update constraints of the sub views of the UIView without using IBOutlets of NSLayoutConstraints.
UIView on Xib:
Code:
class ViewController: UIViewController {
#IBOutlet var scrollView: UIScrollView!
var profileView:UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.profileView = UINib.init(nibName: "ProfileView", bundle: nil).instantiate(withOwner: self)[0] as! UIView
self.scrollView.addSubview(self.profileView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.profileView.layer.frame.size = CGSize(width: self.scrollView.frame.width, height: self.profileView.frame.height)
self.profileView.layer.position = CGPoint(x: self.scrollView.frame.width/2, y: (self.profileView.frame.height/2)+10)
}
}
Output:
Update: More Information
I am aware to set contentSize of the scrollView. I used layer properties of UIView for manipulating height and width of the UIView. Instead of changing height and width also I need to update constraints of the sub views of UIView.
This is an example for understanding. But, In real I will be add more views like that.
Github Repository :
https://github.com/RAJAMOHAN-S/ScrollViewTest
Required output:
Your best bet is to set the constraint of self.profileView programmatically, I've added an example below to get you started.
class TestVC: UIViewController {
#IBOutlet var scrollView: UIScrollView!
private var profileView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.profileView = UINib.init(nibName: "ProfileView", bundle: nil).instantiate(withOwner: self)[0] as! UIView
self.configureProfileView()
}
private func configureProfileView() -> Void {
self.profileView.translatesAutoresizingMaskIntoConstraints = false
self.scrollView.addSubview(self.profileView)
self.profileView.widthAnchor.constraint(equalTo: self.scrollView.widthAnchor).isActive = true
self.profileView.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
// Pin the profile view to the top of the scrollView
self.profileView.topAnchor.constraint(equalTo: self.scrollView.topAnchor).isActive = true
}
}
More information can be found here too: https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html
Note: I'm pretty new working with iOS UI.
I want to create a custom view that stacks a custom view inside.
So I created the custom UIStackView
class CustomStackView: UIStackView {
func addItem(color:UIColor){
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "RowView", bundle: bundle)
let rowView = RowView();
let view = nib.instantiate(withOwner: rowView, options: nil).first as! UIView
rowView.addSubview(view)
rowView.view.backgroundColor = color;
addArrangedSubview(rowView)
}
}
class RowView :UIView{
#IBOutlet var view: UIView!
override public var intrinsicContentSize: CGSize {
return CGSize(width: view.frame.width,height:view.frame.height)
}
}
in the RowView.xib I created a simple layout for testing:
Simulated Metrics = Freeform
Height = 100
And the ViewController.swift:
class ViewController: UIViewController {
#IBOutlet weak var customStackView: CustomStackView!
#IBOutlet weak var constraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
customStackView.addItem(color: UIColor.red)
customStackView.addItem(color: UIColor.blue)
customStackView.addItem(color: UIColor.green)
}
#IBAction func click(_ sender: Any) {
constraint.constant = -customStackView.frame.height
UIView.animate(withDuration: 4, animations: {
self.view.layoutIfNeeded();
},completion:nil)
}
}
The result:
The first and second item are displayed correctly but the third is higher than expected.
In addition if I click the button (which should hide the Stackview) keep the "extra" height visible:
How can I fix that?
Edit: Tried the #KristijanDelivuk solution adding a trailing view. And didn't work. Adding cyan color to the view I got this result:
override func viewDidLoad() {
super.viewDidLoad()
customStackView.addItem(color: UIColor.red)
customStackView.addItem(color: UIColor.blue)
customStackView.addItem(color: UIColor.green)
let view = UIView();
view.heightAnchor.constraint(equalToConstant: 50).isActive = true;
view.backgroundColor = UIColor.cyan;
customStackView.addArrangedSubview(view)
}
You can try adding an empty UIView as your last element of UIStackView:
So your hierarchy should look something like this:
- STACKVIEW
-- 1ST ADDED CUSTOM VIEW
-- 2ND ADDED CUSTOM VIEW
-- 3RD ADDED CUSTOM VIEW
-- EMPTY UIVIEW
Empty UIView will take all unallocated space from 3rd view and all should be displayed correctly.
For repositioning button after hiding/showing stackview you can create for example "top constraint" and then on tap change top constraint height to (-) stackview.height or (+) stackview.height - This shouldn't be any problem.
I have created a UIStackView in IB which has the distribution set to Fill Equally. I am looking to get the frame for each subView but the following code always returns (0, 0, 0, 0).
class ViewController: UIViewController {
#IBOutlet weak var stackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
let pView = UIView()
let sView = UIView()
pView.backgroundColor = UIColor.red
sView.backgroundColor = UIColor.orange
stackView.addArrangedSubview(pView)
stackView.addArrangedSubview(sView)
}
override func viewDidLayoutSubviews() {
print(stackView.arrangedSubviews[0].frame)
print(stackView.arrangedSubviews[1].frame)
}
}
I would think that a stack view set to fill equally would automatically set the calculate it.
Any help would be appreciated.
After reading over your code I think this is just a misunderstanding of viewDidLayoutSubviews(). Basically it is called when all the views that are descendants of the main view have been laid out but this does not include the subviews(descendants) of these views. See discussion notes from Apple.
"When the bounds change for a view controller's view, the view adjusts the positions of its subviews and then the system calls this method. However, this method being called does not indicate that the individual layouts of the view's subviews have been adjusted. Each subview is responsible for adjusting its own layout."
Now there are many ways to get the frame of the subviews with this being said.
First you could add one line of code in viewdidload and get it there.
override func viewDidLoad() {
super.viewDidLoad()
let pView = UIView()
let sView = UIView()
pView.backgroundColor = UIColor.red
sView.backgroundColor = UIColor.orange
stackView.addArrangedSubview(pView)
stackView.addArrangedSubview(sView)
stackView.layoutIfNeeded()
print(stackView.arrangedSubviews[0].frame)
print(stackView.arrangedSubviews[1].frame)
}
OR you can wait until viewDidAppear and check there.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print(stackView.arrangedSubviews[0].frame)
print(stackView.arrangedSubviews[1].frame)
}