UIViewController view's frame changes after present(viewController: animated: completion: ) - ios

I have a UIViewController that I set its size in ViewDidLoad like that:
//Setting view's frame
let width = UIScreen.main.bounds.size.width - 10
let height = (43 * UIScreen.main.bounds.height) / 100 //%43 of the screen
self.view.frame = CGRect(x: 5, y: 80, width: width, height: height)
It works great until I try to present another ViewController like that:
//Presenting AutoCompleteVC
self.autocompleteVC = GMSAutocompleteViewController()
self.autocompleteVC.delegate = self
self.present(self.autocompleteVC, animated: true, completion: nil)
After I dismiss this view (self.autocompleteVC.dismiss(animated: true, completion: nil)) the frame of the first ViewController's view changes to the full screen's frame.
Any idea how to fix it?
Thanks!

Set your frame in viewWillLayoutSubviews. The frame is already known then. Like this:
public override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
//Setting view's frame
let width = UIScreen.main.bounds.size.width - 10
let height = (43 * UIScreen.main.bounds.height) / 100 //%43 of the screen
self.view.frame = CGRect(x: 5, y: 80, width: width, height: height)
}

I suggest that write your code in viewWillAppear but after set frame, you should write
self.view.setNeedsLayout()
I hope this will work.

The main view of a view controller is positioned and sized by system logic depending on landscape/portrait, splitscreen, modal display, view controller containment...
Instead you should do every content layouting in subviews (depending on main view size) and set background color of the main view to UIColor.clear.
So in your case add a subview and configure it as you wish.
Hint: You should do it with auto layout instead of manipulating the frame.

Related

animated expanding of subview by action without constraints

I have a UIViewController with a UIScrollView as subview. the scrollview size and origin are computed properties.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let sizeWidth = view.frame.size.width - (view.frame.size.width / 5)
let sizeHeight = (view.frame.height / 5) * 3
let originX = (view.width-sizeWidth) / 2
// sizes & positions of container & subviews
slideScrollView.frame = CGRect(x: originX,
y: 50,
width: sizeWidth,
height: sizeHeight)
how it looks like
by tapping the sign in or login button, the scrollview should expand animated. but it is the opposite.
#objc private func loginButtonTapped() {
UIView.animate(withDuration: 5, delay: 0, options: .allowAnimatedContent) {
self.slideScrollView.frame.size.height += (self.view.frame.height / 5) * 1
}
}
this is the result
it should expand, but it sets back to the action height property and expand to regular size,
i hope anyone can tell me why this happens and may have a solution.
Thats because your viewDidLayoutSubviews gets called multiple times (In this case twice as I have noticed by adding debug statements) when you start your animation with UIView.animate(withDuration: and because you always reset the slideScrollView.frame in viewDidLayoutSubviews you see unnecessary side effects.
You can always check this by putting a break point in viewDidLayoutSubviews when loginButtonTapped gets triggered. Refer this question for similar explaination
If your intention to use viewDidLayoutSubviews is to know when view is properly loaded and its frames are updated properly you can always use
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let sizeWidth = view.frame.size.width - (view.frame.size.width / 5)
let sizeHeight = (view.frame.height / 5) * 3
let originX = (view.width-sizeWidth) / 2
// sizes & positions of container & subviews
slideScrollView.frame = CGRect(x: originX,
y: 50,
width: sizeWidth,
height: sizeHeight)
}
Now your loginButtonTapped should work fine.
As per apple docs: viewDidLayoutSubviews Called to notify the view controller that its view has just laid out its subviews.
With that I think my explanation above makes sense, you use UIView.animate(withDuration: to modify the frame of scrollView so obviously View updates/re-renders the subViews hence viewDidLayoutSubviews gets called when you call UIView.animate(withDuration: and because of your frame update code in viewDidLayoutSubviews you see adverse side effects

Increasing frame.origin.x vs constant

I'm trying to understand how iOS frame and bounds works.
I put an subView:UIView on UIViewController and a button which can increase subView's frame origin coordinate and change textlabel with its value.
like this,
let subView: UIView = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(subView)
subView.backgroundColor = .blue
}
#IBAction func btnMoveBottomView(_ sender: Any) {
subView.frame.origin.y = subView.frame.origin.y + 100
lbFrameInfo.text = String(format:"sub = (%.1f, %.1f)", subView.frame.origin.x, subView.frame.origin.y)
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
After I make this action, I see subView go down but text doesn't change.
on debug console I see this change
po subView.frame.origin
▿ (50.0, 2350.0)
- x : 50.0
- y : 2350.0
updateViewConstraints has been called too.
override func updateViewConstraints() {
print("updateViewConstraints")
super.updateViewConstraints()
}
Instead of increasing frame.origin.x like this, increasing value of leading constant works perfectly.
It would be appreciated if someone can guide me the differences of these and the concept of frame and if it is related with auto-layout things
According to your question,
You have added subView to ViewController view so when u create view
let subView: UIView = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
and do this
view.addSubview(subView)
it means your subview will be at 50 position down and away from left of the screen.
so when u do this.
subView.frame.origin.y = subView.frame.origin.y + 100
it will move your frame further down in the screen as you are changing the y position of frame which is the starting position of creating your view on screen.
At its simplest, a view’s bounds refers to its coordinates relative to its own view (as if the rest of your view hierarchy didn’t exist), whereas its frame refers to its coordinates relative to its parent’s view. Frame will reflect the position in its parents view.
This means a few things:
1.If you create a view at X:0, Y:0, width:100, height:100, its frame and bounds are the same.
2.If you move that view to X:100, its frame will reflect that change but its bounds will not. Remember, the bounds is relative to the view’s own space, and internally to the view nothing has changed.
3.If you transform the view, e.g. rotating it or scaling it up, the frame will change to reflect that, but the bounds still won’t – as far as the view is concerned internally, it hasn’t changed
hope it clears your doubts

childViewController.view.frame in UIView present different value

I use childViewController to separate view in project, but some strange issue happened, here is my code.
class ViewController: UIViewController {
var container = UIView()
var childVC = ChildViewController()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
addChildViewController(childVC)
childVC.didMove(toParentViewController: self)
addChildView()
setContainerFrame()
}
func setContainerFrame() {
container.frame = CGRect(x: 0, y: 100, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height - 100)
container.backgroundColor = .red
view.addSubview(container)
}
func addChildView() {
let frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
childVC.view.frame = frame
childVC.view.backgroundColor = .green
container.addSubview(childVC.view)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("childVC.view.frame: \(childVC.view.frame)")
}
}
class ChildViewController: UIViewController {
}
when I exchange the order invoking func addChildView() and func setContainerFrame() in ViewController's viewDidLoad(), xcode'console prints different frame log.
This is due to the autoresizingMask of the childVC.view. By default this is set to have flexible width and height. So if you set the frame of the container and then the frame of the childVC.view then you get the actual value you set but if you reverse it and set the frame of the childVC.view and then the container the frame of the childVC.view is automatically updated to have the same relative width and height.
For example if the frame size for the container was 100 x 100 and then you change it to 200 x 200 the frame size for childVC.view will be doubled.
To remove this set the childVC.view.autoresizingMask = [] so it remains as you set it.
The strange behavior occurs because in addChildView() function you are adding the child view controller's view as subview to container view but container view's frame is setting in the function setContainerFrame() which is not yet called. That means you are adding a subview to a view whose frame hasn't set already.

The bottom content of the scrollView is not not responding to touch events

I have scroll view. In side scroll view I have a container view with trailing,leading,top and bottom constraints and also equal heights. I'm adding views dynamically. I'd updated the content size in
viewWillLayoutSubviews as follows.
override func viewWillLayoutSubviews() {
self.scrollView.contentSize = CGSize(width: self.contentView.frame.width, height: self.contentView.frame.height)
self.contentView.frame = CGRect(x: self.contentView.frame.minX, y: self.contentView.frame.minY, width: self.contentView.frame.width, height: 2500) // code to update the contentView's frame where 2500 is a dummy value
self.contentView.clipsToBounds = true
}
Now I can scroll until my last subview of the container view. But the problem is the last 3 subviews are not responding to the touch events.
I tried the following ways but failed.
also updated the containterView's frame
also updated the scrollview's frame
did not work.
Please help me.
func updateFrame() {
self.scrollView.contentSize = CGSize(width: self.contentView.frame.width, height: self.contentView.frame.height)
self.contentView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.frame = CGRect(x: self.contentView.frame.minX, y: self.contentView.frame.minY, width: self.contentView.frame.width, height: 2500) // code to update the contentView's frame where 2500 is a dummy value
self.contentView.clipsToBounds = true
}
try this.
I am assuming that your scrollview is bigger then Content view

getting highly confused by odd behaviour of viewController Method

I have overrided a method as
override func viewDidLayoutSubviews() {
// creating bottom line for textField
let border = CALayer()
let width = CGFloat(1.0)
border.borderColor = UIColor.whiteColor().CGColor
border.frame = CGRect(x: 0, y: emailTextField.frame.size.height - width, width: emailTextField.frame.size.width, height: emailTextField.frame.size.height)
border.borderWidth = width
emailTextField.layer.addSublayer(border)
emailTextField.layer.masksToBounds = true
}
Now whats happening is that when I run my app on Iphone 6, 6+ every thing works fine. But when I run the same code on iphone5 (Simulator + real Device ) viewDidLayoutSubViews is getting called infinite times and my app becoms unresponsive. I solved the problem by using a bool variable. But I do not understand why is this happening. So can someone please explain this to me.
Thanks :-)
As docs says that viewDidLayoutSubviews method need for change layout
Called to notify the view controller that its view has just laid out
its subviews. 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.
Move your code to viewDidLoad method or to loadView if you are not using xibs or Storyboards
override func viewDidLoad() {
// creating bottom line for textField
let border = CALayer()
let width = CGFloat(1.0)
border.borderColor = UIColor.whiteColor().CGColor
border.frame = CGRect(x: 0, y: emailTextField.frame.size.height - width, width: emailTextField.frame.size.width, height: emailTextField.frame.size.height)
border.borderWidth = width
emailTextField.layer.addSublayer(border)
emailTextField.layer.masksToBounds = true
}
If you would like to change border.frame you can set it to class variable and change it in viewDidLayoutSubviews method
override func viewDidLayoutSubviews() {
self.border.frame = CGRect(x: 0, y: emailTextField.frame.size.height - width, width: emailTextField.frame.size.width, height: emailTextField.frame.size.height)
self.border.borderWidth = width
}
Just replace
emailTextField.layer.addSublayer(border)
with this:
emailTextField.addSublayer(border)
-> you want to add sublayer to a view - as you want to apply masksToBounds property to it either.
Move your implementation inside viewDidLayoutSubviews. I got the same issue. Hope it will work.
override func viewDidLayoutSubviews()
{
//Your CODE
}

Resources