Increasing frame.origin.x vs constant - ios

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

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

UIImageView frame not placing correctly on the parent ImageView

I have a color palette imageview, in that I want to place a plus icon(imageView) according to the x and y-axis I am getting from the backend. If I get x = 0 and y = 0 the frame of the plus is placing correctly
For y axis if I set height of the color palette imageView to the plus icon's frame's y axis, the icon is not going to the actual (0,0)
.
The code I used is below
let cWidth = self.colorPalleteImageView.frame.size.width // 348
let cHeight = self.colorPalleteImageView.frame.size.height // 378.5
let imageView = UIImageView(image: appImages.roundPlusIcon.image)
imageView.frame = CGRect(x: 0, y: cHeight, width: 22, height: 22)
colorPalleteImageView.addSubview(imageView)
I am checking this with iPad 12.9 inch simulator. Am I missing anything to achieve that x=0 and y=0, If I give the width of the colorPaletteImage to the x-axis of plusIconImageView it is not going to end fo the x-axis, It stays before the end of the width of the imageview, I don't know why it is happening, Need help
You're using frame sizes before the frames are finished being set by auto-layout.
I'd suggest using constraints, but if you want to stick to frame coordinates...
add the "round plus" icon imageView in viewDidLoad()
set its frame.origin in viewDidLayoutSubviews() or viewDidAppear()
imageView.frame = CGRect(x: cWidth, y: cHeight, width: 22, height: 22)
mast be:
imageView.frame = CGRect(x: 0, y: 0, width: 22, height: 22)
as axes are:
0,0-----------------> x
| ▢
|
|
|
V
with 0,0 you put top left of little image to to top left of big.
You can fix this issue by modifying the height property to the super view's bound's height. You need to know the difference between frame and bounds to understand why this is happening. In simple words frame is the CGRect with respect to it's super view and bounds is the CGRect with respect to it's own coordinates.
Plenty of detailed explanations are available online just need to google frame vs bounds and you'll get used to both of these after you play with it many times.
Here's how you fix this issue.
let cHeight = self.colorPalleteImageView.bounds.size.height
sorry.. I am back
I did (to follow your code I forced values..)
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var colorPalleteImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let v = self.view
print(v!.frame)
let cHeight = CGFloat(378.5)
colorPalleteImageView.frame.size.width = 348
colorPalleteImageView.frame.size.height = cHeight
let plusImg = UIImage(named: "plus")
let imageView = UIImageView(image: plusImg)
imageView.frame = CGRect(x: 0, y: cHeight-22, width: 22, height: 22)
colorPalleteImageView.addSubview(imageView)
}
}
and I got: (on iPad)

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

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.

Programmatic UIView with subview not rendering in correct position

I'm trying to render a view that has a subview in it. However, the subview renders in the incorrect y-position.
ViewController is the parent view.
ViewController2 is a subview of ViewController and has it's own subview (let's call it X). X is being rendered in the incorrect y-position, even though ViewController2 and X have the same y value for their frames.
Edit:
I should note, X should be appear in the same position within ViewController2. Or at least, that's the intention.
See code below:
ViewController.swift
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
var y: CGFloat = 10
for vc in getVcs() {
self.view.addSubview(vc.render(x: 10, y: y))
y += 75
}
super.viewDidLoad()
}
func getVcs() -> [ViewController2] {
return [
ViewController2(),
ViewController2(),
ViewController2()
]
}
}
ViewController2.swift
import UIKit
class ViewController2: UIViewController {
func render(x: CGFloat, y: CGFloat) -> UIView {
let viewFrame = CGRect(x: x, y: y, width: 300, height: 50)
let xview = UIView(frame: viewFrame)
let subViewFrame = CGRect(x: x, y: y, width: 200, height: 25) // X
let subView = UIView(frame: subViewFrame)
subView.layer.borderColor = UIColor.green.cgColor
subView.layer.borderWidth = 1
xview.addSubview(subView)
xview.layer.borderColor = UIColor.red.cgColor
xview.layer.borderWidth = 1
return xview
}
}
How they appear at runtime:
(Green border represents X and the red border represents ViewController2.)
Any help would be appreciated!
A view defines its own frame in relation to its parent view.
In ViewController2, the subview you instantiated based on y parameter is getting added in relation to its parent view (the red box).
The solution is to change y value in relation to parent frame on your subview frame
let subViewFrame = CGRect(x: x, y: y, width: 200, height: 25) // X
Also to get a clear idea what's happening, try adding another ViewController2() in getVCs() and it will look like this.
As you can see, your code is not misplacing the second view as it looks like from your screen shot. It's placing the green box further and further in relation to its parent frame. Third frame was just a lucky hit. Hope this helps =)
Frames are specified in their SUPERVIEW'S COORDINATES. Your bounds are in the current view's coordinates. Therefore ViewController2's frame needs to be expressed as relative to ViewController 's frame, not relative to the window.

How to create percentage of total width using autolayout?

I need to create three dynamic columns, each with a fixed percentage of the total width. Not thirds, but different values. For example, the following illustration shows three columns: the first being 42% wide, the second being 25% wide, and the third being 33% wide.
For a 600 pixel across viewcontroller, that would be 252, 150, and 198 pixels respectively.
However, for any subsequent display sizes (i.e. iPhone 4 landscape (960 wide) or iPad 2 portrait (768 wide), I would like the relative percentages to be the same (not the pixel widths quoted above).
Is there a way to do this using Storyboards (i.e. without code)? I can do this easily in code, but my goal is to put as much of this display logic as possible into the Storyboard.
If, as you say, you know how to do it in code, then you already know how to do it in the storyboard. It's exactly the same constraints, but you are creating them visually rather than in code.
Select both a view and its superview.
Choose Editor -> Pin -> Widths Equally to constrain the width to be equal to the superview's width (actually the "pin" popup dialog at the bottom of the canvas works best here).
Edit the constraint and set the Multiplier to the desired fraction, e.g. 0.42. And so too for the other views.
As Apple introduces UIStackView it made job much easy.
Method 1: Using Nib/StoryBoard:
You have to just add three view in interface builder & embed them into stackview
Xcode ► Editor ► Embed in ► StackView
Select stackView & give constraint with leading, trailing, top & equal height with safeArea
Click to Attribute inspector area &
Set StackView horizontal & distribution to fill proportionally
[3
Give constraint of three view with leading, trailing, top, bottom with respective of sides.
Method 2: Programmatically:
import UIKit
class StackViewProgramatically: UIViewController {
var propotionalStackView: UIStackView!
///Initially defining three views
let redView: UIView = {
let view = UIView()//taking 42 % initially
view.frame = CGRect(x: 0, y: 0, width: 42 * UIScreen.main.bounds.width/100, height: UIScreen.main.bounds.height)
view.backgroundColor = .red
return view
}()
let greenView: UIView = {
let view = UIView()//taking 42* initially
view.frame = CGRect(x: 42 * UIScreen.main.bounds.width/100, y: 0, width: 25 * UIScreen.main.bounds.width/100, height: UIScreen.main.bounds.height)
view.backgroundColor = .green
return view
}()
let blueView: UIView = {
let view = UIView()//taking 33*initially
view.frame = CGRect(x: 67 * UIScreen.main.bounds.width/100, y: 0, width: 33 * UIScreen.main.bounds.width/100, height: UIScreen.main.bounds.height)
view.backgroundColor = .blue
return view
}()
///Changing UIView frame to supports landscape mode.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
DispatchQueue.main.async {
self.redView.frame = CGRect(x: 0, y: 0, width: 42 * self.widthPercent, height: self.screenHeight)
self.greenView.frame = CGRect(x: 42 * self.widthPercent, y: 0, width: 25 * self.widthPercent, height: self.screenHeight)
self.blueView.frame = CGRect(x: 67 * self.widthPercent, y: 0, width: 33 * self.widthPercent, height: self.screenHeight)
}
}
override func viewDidLoad() {
super.viewDidLoad()
//Adding subViews to the stackView
propotionalStackView = UIStackView()
propotionalStackView.addSubview(redView)
propotionalStackView.addSubview(greenView)
propotionalStackView.addSubview(blueView)
propotionalStackView.spacing = 0
///setting up stackView
propotionalStackView.axis = .horizontal
propotionalStackView.distribution = .fillProportionally
propotionalStackView.alignment = .fill
view.addSubview(propotionalStackView)
}
}
//MARK: UIscreen helper extension
extension NSObject {
var widthPercent: CGFloat {
return UIScreen.main.bounds.width/100
}
var screenHeight: CGFloat {
return UIScreen.main.bounds.height
}
}
Output:
Works with landscape & portrait
Demo project - https://github.com/janeshsutharios/UIStackView-with-constraints
https://developer.apple.com/videos/play/wwdc2015/218/
I think this can be explained in more detail so it can be more easily applied to any number of views requiring fixed percentage layouts within a superview.
Left-most view
Anchored to SuperView.Leading
Defines its fixed percentage as a multiplier on the SuperView.Height
Intermediate views
Defines its fixed percentage as a multiplier on the SuperView.Height
Pins its left to its neighbor's right
Right-Most view
Does not define a fixed percentage (it is the remainder of the available view)
Pins its left to its neighbor's right
Pins its right to SuperView.Trailing
All Views
Define their non-fixed heights by anchoring to Top Layout Guide.Top and Top Layout Guide.bottom. In the answer above, it is noted that this can also be done by setting equal height to the neighboring view.

Resources