I have three child VCs that are added to a parent VC. In one of my child VC, I have a view being loaded from a nib. In that nib, I set up a UITextView with no constraints and disabled scrolling so that it dynamically sizes depending on the text. This all works fine so far, I can enter or remove text from the UITextView and it resizes accordingly.
However, if I switch tabs (child VCs), and return back to the one with my UITextView, my UITextView is now of height 0 (cannot be seen). I'm not sure what is causing this to happen, everything works fine until I switch views and return.
My first thought would be to reconfigure my UITextView on viewDidAppear() when returning to my child VC, except my UITextView outlet and setup method is in a separate UIView subclass so I cannot call viewDidAppear(). I'm not even sure if that would be a fix, just what I would guess.
MyParent.swift: UIViewController
MyChild1.swift: UIViewController
MyChild2.swift: UIViewController
MyChild3.swift: UIViewController
MyView.swift: UIView - Custom View loaded from Nib
(this is where my UITextView outlet is and setup for it)
In MyChild3 for example I create the custom view with:
let view = Bundle.main.loadNibNamed("MyView", owner: self, options: nil)?.first! as! MyView
view.myModel = model
view.configure()
Then, in MyView.swift which is the custom class that the nib uses, I have my UITextView outlet and setup method:
#IBOutlet weak var textView: UITextView!
public func configure() {
configureTextView()
}
private func configureTextView() {
textView.delegate = self
textView.text = myModel.text
textView.isScrollEnabled = false
}
EDIT:
I should mention that my UITextView is a part of a horizontal StackView along with a UIImageView, as follows:
UIImageView
UITextView
When I return to my VC, the UIImageView takes up 100% of the height in the StackView, although before leaving my VC, the UITextView height was sized correctly to its text. After doing Show View Hierarchy, I cannot even find my UITextView anymore, only the UIImageView seems to be in the StackView. I essentially want my UITextView to take up as much height as it needs in the StackView, and the UIImageView take up the rest, but the UIImageView is taking it all up.
I did manage to find this error?
I would try setting content compression resistance of the textView to a higher value, maybe that's what causing ambiguity:
textView.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 999), for: .vertical)
Related
I have a Scroll View set to a fixed height inside my View Controller. I want to use a navigation bar on top with large titles, so when i scroll the Scroll View, it should collapse like in a Navigation Controller. Is it possible to do this? My scene look like this:
The navigation bar has top/left/right 0 constraints agains the View. Currently it stays on top correctly, however it won't collapse on scroll as expected.
Do not use a "loose" navigation bar like this. Use a navigation controller, even if you do not intend to do any navigation. It gives you the desired behavior, for free.
In the end i created a custom view to replicate the Navigation Bar. Here you can see how it looks and read the steps below to replicate:
To setup your View Controller to be used with a custom Scroll View, first make sure you are using Freeform size for your controller. To do this, select Freeform in the size inspector and set the height to your new Scroll View's height:
Insert your Scroll View and setup 0 top/left/right/bottom constraints, so it will be the same size as your View Controller:
Add your content to your scroll view as usual
Now to create your custom Navigation Bar, add a View outside of your Scroll View and setup constraints like this:
Notice a few things:
the top constraint is aligned to the Superview instead of the Safe Area, so the view goes behind the status bar
The height is set to >= 44, so its a minimum height and can expand if the content is larger
On the Attribute Inspector, select clip to bounds, so your content inside the view won't overflow(like in CSS, overflow:hidden)
At this point you might see some errors in your Storyboard, but don't worry about it: its because you don't have any content in your View and it doesn't know how tall it should be
Set the View background to transparent and add a "Visual Effect View with Blur" inside, with 0 top/left/right/bottom constraints. This will blur the content behind the custom navigation bar
Now make sure that you check the Safe Area Layout Guide checkbox in your navigation bar view(its above the constraints setup):
This way you can add content inside the view that won't be behind the status bar, because its outside of the safe area. And it works with the notch too.
Add a label inside your view, set top and bottom constraints to Safe Area and make sure you have a fixed height constraint defined too:
Now you can also see that the errors in your Storyboard are gone :) At this point this is how everything should look like:
Now the coding part. In your ViewController, make outlets for both the ScrollView and the custom navigation bar. To do this, switch to the assistant editor(the venn-diagram symbol top right), select the element in your storyboard, hold down CTRL and drag inside your ViewController class:
Do the same for your View that is your navigation bar:
#IBOutlet weak var mainScrollView: UIScrollView!
#IBOutlet weak var customNavigationBar: UIView!
Next, you need to add the UIScrollViewDelegate to your class, so you can listen to the scroll event and get the actual Y position of the current scroll offset using the scrollViewDidScroll function:
class ViewController: UIViewController, UIScrollViewDelegate {
You also need to setup the delegate in your viewDidLoad hook:
mainScrollView.delegate = self
Create a new function called scrollViewDidScroll to get the scroll position and you can use this to do various animations with other elements. In this case, if the scroll position reaches 44(this is the height i set for my custom navigation bar), it will animate to full opacity:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let y = self.mainScrollView.contentOffset.y
let barHeight = 44
if(y < barHeight) {
customNavigationBar.alpha = y/CGFloat(barHeight)
} else {
customNavigationBar.alpha = 1
}
}
You can use the same logic to animate the label inside the navigation bar, change its size etc...
The full ViewController:
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var mainScrollView: UIScrollView!
#IBOutlet weak var customNavigationBar: UIView!
override func viewDidLoad() {
super.viewDidLoad()
mainScrollView.delegate = self
customNavigationBar.alpha = 0
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let y = self.mainScrollView.contentOffset.y
let barHeight = 44
if(y < 44) {
customNavigationBar.alpha = y/CGFloat(barHeight)
} else {
customNavigationBar.alpha = 1
}
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var height = CGFloat()
if(scrollView.panGestureRecognizer.translation(in: scrollView.superview).y > 0) {
height = 130
}
else {
height = 44
}
UIView.animate(withDuration: 0.5) {
self.navBarHeightConstraint?.constant = height
self.view.layoutIfNeeded()
}
}
I have a UIView on a XIB, containing an UIImageView and a UILabel with a small space in between. Horizontal layout is a simple chained |--image--label--| in which -- is some fixed space. The height is fixed to 40, this view is horizontally centred in its view controller, and it has an >= 100 width constraint.
If I change the label text, the width of my composed view updates as expected width the changed width of its label, and it stays nicely centred on the view controller.
Because I need this UIView, containing an image and label, in other places, I've created a custom class consisting of a XIB and Swift file. Let's call it ItemView.
Issue I have is that the empty UIView on my view controller XIB, which I've changed class to ItemView, no longer accepts the >= 40 width constraint. This is of course because the view controller XIB no longer sees the variable width UILabel, but instead just a plain UIView of class ItemView. I get an 'Inequality Constraint Ambiguity' IB error.
The result is that the width of my custom view remains 40. It works a little bit if I specify a larger >= label width; the text is then only cut off when this width is reached. But in that second case my custom view is no longer horizontally centred, but shifted a bit to the left instead.
How do I resolve this? Or, how can I tell IB to treat my custom ItemView in a similar way as a UILabel?
In my ItemView I've done all I could find:
override class var requiresConstraintBasedLayout: Bool
{
return true
}
Call setNeedsLayout() after setting the label text.
Call myLabel.translatesAutoresizingMaskIntoConstraints = false.
Call self.translatesAutoresizingMaskIntoConstraints = false.
And call self.setNeedsUpdateConstraints() in both init()s.
Configure this pop-up in the view's Size inspector:
Now IB won't worry about your size specifications. You know better than IB does, and this is how to tell IB that fact.
Another way: configure this pop-up in the view's Size inspector:
This tells IB that the view will have an intrinsic content size that it doesn't know about.
Either of those will work. As you can see in this screenshot, I've given my custom view a width constraint of greater-than-or-equal-to-40, but IB is not complaining of any error:
How I structured my custom UIView: My XIB's Files Owner is ItemView. ItemView has an #IBOutlet var: UIView! that's connected to the view in the XIB. Then I have:
init()
{
super.init(frame: .zero)
self.initView()
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
self.initView()
}
private func initView()
{
Bundle.main.loadNibNamed("ItemView", owner: self, options: nil)
self.view.frame = CGRect(x: 0.0, y: 0.0, width: self.width, height: self.height)
self.addSubview(self.view!)
}
(I seems this extra UIView is needed when creating a custom UIView with a XIB. Would love to hear if it's not needed after all.)
To make it work I needed to call self.invalidateIntrinsicContentSize() after updating the label. And override intrinsicContentSize:
override open var intrinsicContentSize: CGSize
{
return self.view.systemLayoutSizeFitting(self.bounds.size)
}
Note that I'm calling this on self.view, not on self.
Thanks #matt for pointing me in the right direction. Was the first time I've encountered something like this.
The following things I tried we all not necessary:
override class var requiresConstraintBasedLayout: Bool
{
return true
}
Call setNeedsLayout() after setting the label text.
Call myLabel.translatesAutoresizingMaskIntoConstraints = false.
Call self.translatesAutoresizingMaskIntoConstraints = false.
And call self.setNeedsUpdateConstraints() in both init()s.
I have the following scenario:
I have a xib file of name View1.xib which contain some stack views containing certain set of buttons.
View1.xib UIView
Im having a coco touch class of type UIView for loading the Nib named View1. Also I have made it as owner of the previous xib. Below is the code I'm using to load the view:
override init(frame: CGRect) {
super.init(frame: frame)
commitInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commitInit()
}
private func commitInit() {
Bundle.main.loadNibNamed("View1", owner: self, options: nil)
containerView.frame = self.bounds
containerView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
addSubview(containerView)
}
Now in the storyboard I included the scrollview and a sub UIView under it. I have defined the Subview type as View1.
ScrollView in main storyboard
Also I have defined proper constraints for the Views making it equal height and width as the Parent ScrollView.
Scrollview constraints
Also find the scrollview properties defined as below:
ScrollView Properties
This is how the screen looks like on emulator :
Emulator
Now when I'm running the project I'm getting the screen but I'm unable to scroll through the view included. Due to this some elements on top and bottom becomes hidden (Out of bounds) and I'm unable to scroll as well. Am I doing anything wrong? Please help me out.
Note - I'm very new to swift, any help is greatly appreciated. Thanks in advance.
Your newView can't flow out of scrollView's bound, Because it has same height with your scrollView. But your newView's content flows out of newView's bounds.(You can see if you put your newView out of scrollView with same height and set it a backgroundColor or set clipToBounds property true) That's why you can't scroll.
To Sove This, you should find another source for your newView's height(Maybe a constant value or its subview's heihgt or safeArea.heihgt . This is your choice)
For subview add
yourView.translatesAutoresizingMaskIntoConstraints = false, then left, right, top, bottom constraint programatically then add subview.
For scroll view set the height constraint for inner view with the scroll view. Make the outlet of that constraint and then set dynamic height of the scrollview outlet.
I have a UIImageView and UIButton, UIButton is aligned to top of UIImageView. Now in code I am changing the top of UIImageView but the UIButton is not updated accordingly. I tried SetNeedsLayout, LayoutIfNeeded on UIButton but nothing works.
override func viewDidLayoutSubviews() {
profileImageView.frame.origin.y = arcView.bounds.height/1.8
editProfileButton.layer.setNeedsDisplay()
}
I have set the constraints in Storyboard. I would really appreciate any help here.
First: Are you sure that all your constraints (for both the UIImageview and UIButton) are added right?
Second: when working the constraints, you should change the origin.y of the UIImageview also by by a constraint, by modifying its constant's value:
Instead of directly changing profileImageView.frame.origin.y, you should change the constant of the constraint that tells what's the imageview origin.y (if the first point is applied, this constraint must be exist...); Add this constraint to the viewController as an IBOutlet and change value of its constant property (take a look at the comments in the code snippet, it's a part of the answer):
class ViewController: UIViewController {
// let's assume that this is the #IBOutlet of the constraint, I called it 'imageViewTopMargin':
#IBOutlet weak var imageViewTopMargin: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// it's not necessary -for sure- to do this the in the viewDidLoad method
// but I'm doing this for demo purposes:
// let's say that you want to push the imageView to the bottom with extra 40 points:
imageViewTopMargin.constant += 40
}
}
Hope this helped.
I am trying to show 2 UITableViewController's inside an horizontal UIScrollView. I am using AUtoLayout and Size Classes. My storyboards are of size Inferred. This is what I did for my test:
Add an UIScrollView and choose Update Constraints for AutoLayout from Resolve AutoLayout Issues.
Setup one UITableViewController and one UIViewController which inherits from UITableViewDelegate and UITableViewDataSource (I wanted to try both ways to use table views)
For both of them I added an UIButton on the right hand side and for AutoLayout I choose top and trailing edge to be fixed on the right.
After I run the code for UITableViewController I can only the "n" from Button if I click on the cell, I can see the button.
For the UIViewController table view I don't see the button at all.
Here is a print screen:
And here is my code for the scroll view controller:
override func viewDidLoad() {
super.viewDidLoad()
self.automaticallyAdjustsScrollViewInsets = false
let testTableVC = self.storyboard!.instantiateViewControllerWithIdentifier("TestTableViewController") as! TestTableViewController;
let testVC = self.storyboard!.instantiateViewControllerWithIdentifier("TestViewController") as! TestViewController;
var bounds = UIScreen.mainScreen().bounds
var width = bounds.size.width
var height = bounds.size.height;
scrollView!.contentSize = CGSizeMake(2*width, height);
let viewControllers = [testTableVC, testVC]
var idx:Int = 0;
for viewController in viewControllers {
// index is the index within the array
// participant is the real object contained in the array
addChildViewController(viewController);
let originX:CGFloat = CGFloat(idx) * width;
viewController.view.frame = CGRectMake(originX, 0, width, height);
scrollView!.addSubview(viewController.view)
viewController.didMoveToParentViewController(self)
idx++;
}
}
edit:
https://github.com/adrianstanciu24/scrollviewtest
I've created pull request that solves this. What was wrong is that you've used default UITableViewCell and added some custom content to it in storyboard. Table view then dequeued the default cell and drawn the default cell atop of the one from storyboard (with margins on sides - so only right part of your button was visible). You need to create custom UITableViewCell, connect outlets and dequeue the custom cell, the default one doesn't know anything about your button.