Why iOS coordinate system is difficult to understand?? only me? - ios

I'm studying iOS' UIView!
And I found that I can't understand how bounds works.
For example,
Please run this code ... and see red box's moving.
The red box goes up! and white root view is static!
Why!? why red box goes up! ?? please let me know OTL!
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let childView : UIView = UIView(frame: CGRect(x: 50, y: 50, width: 200, height: 200) )
childView.backgroundColor = UIColor.red
self.view.addSubview(childView)
}
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 8, animations: {
// I cannot unnerstand how bounds works!!
self.view.bounds = CGRect(x: 0, y: -300, width:self.view.bounds.width, height: 700)
//I can understand how frame works
self.view.frame = CGRect(x: 200, y: 0, width: self.view.bounds.width, height: self.view.frame.height)
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I read this article, but I can not understand
CompositedPosition.x = View.frame.origin.x - Superview.bounds.origin.x;
CompositedPosition.y = View.frame.origin.y - Superview.bounds.origin.y;
HERE! this is right ( according to what I tested, it was right. ) but, I don't know why. Why not "View.frame.origin.x + Superview.bounds.origin.x" ?
If we change the origin of view's bounds and the view moves on screen , what is the difference between "bounds" and "frame" in perspective of origin(or position) ? Is there a difference other than simply acting in reverse?

Basically, The bounds of an UIView is the rectangle, expressed as a location (x,y) and size (width,height) relative to its own coordinate system (0,0).
The frame of an UIView is the rectangle, expressed as a location (x,y) and size (width,height) relative to the superview it is contained within.
Please follow this link for clear example. Cocoa: What's the difference between the frame and the bounds?

Related

UIButton subview is offset in subclass

I have a UIButton subclass intended to show a selected state of a button. The selected state simply places a thick black line at the bottom of the button view and when unselected it hides the black line. However, when using this in a UIButton subclass, the black line view is offset. I have tried playing around with insets, but I don't think that is the problem. Here is my subclass:
class TabButton: UIButton {
private var height:CGFloat = 5
private var selectedIndicator:UIView?
override var isSelected: Bool {
didSet {
selectedIndicator?.isHidden = !isSelected
}
}
fileprivate func initializeSelector(_ frame: CGRect) {
selectedIndicator = UIView(frame: CGRect(x: 0, y: frame.size.height - height, width: frame.size.width, height: height))
selectedIndicator?.backgroundColor = UIColor.black
self.addSubview(selectedIndicator!)
}
override func awakeFromNib() {
super.awakeFromNib()
initializeSelector(self.frame)
}
}
The desired button should look like this:
But instead it looks like this:
Can anyone help me understand what is happening here and how to fix it? Thanks!
Try this, in layoutSubviews you get the final frame:
override layoutSubviews() {
super.layoutSubviews()
selectedIndicator.frame = CGRect(x: 0, y: frame.size.height - height, width: frame.size.width, height: height)
}
The frame of the selectedIndicator is set only once when initializeSelector is called. When the button changes its frame, it does not change the frame of subviews, you need to manually update the frame of selectedIndicator.
To do so, you need to override layoutSubviews() method of UIView.
override layoutSubviews() {
super.layoutSubviews()
selectedIndicator?.frame = CGRect(x: 0, y: frame.size.height - height, width: frame.size.width, height: height)
}
See this answer to know when layoutSubviews() is called.

How iOS's coordinate system works? [duplicate]

This question already has an answer here:
Why iOS coordinate system is difficult to understand?? only me?
(1 answer)
Closed 5 years ago.
nice to meet you. I am studying "Coordinate System" of UIView of iOS.
Frame is easy to understand, but Bounds isn't.
For example, Frame works as expected when you change origin.
Bounds does not change its position even if we change origin.
import UIKit
class ViewController: UIViewController {
var childView : UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let rect = CGRect(x: 20, y: 30, width: 200, height: 200)
childView = UIView(frame: rect)
childView.backgroundColor = UIColor.red
self.view.addSubview(childView)
}
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 5, animations: {
self.childView.bounds = CGRect(x: 100, y: 150, width: 200, height: 200)
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
"Why do we need Bounds?" I thought.
And I searched hard on the internet. So, there are some facts I found out.
----First----
Bounds literally means borderline. iOS can draw a picture only within that boundary line. As you can see in the figure above, the UIButton also has Bounds, and when it exceeds the Bounds, the picture is cut and drawn.
---- Second ----
Size of Bounds and Size of Frame are the same.
It's not important, but I tried to test it out.
---- Thirds ----
bounds.origin is also useful in View Hierarchy. But it works differently from Frame.
Frame is easy to understand.
The ViewController's RootView will be 700 from top to button.
import UIKit
class ViewController: UIViewController {
var childView : UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let rect = CGRect(x: 100, y: 100, width: 200, height: 200)
childView = UIView(frame: rect)
childView.backgroundColor = UIColor.red
self.view.addSubview(childView)
self.view.backgroundColor = UIColor.cyan
}
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 10, animations: {
self.view.frame = CGRect(x: 0, y: 700, width: self.view.frame.size.width, height: self.view.frame.size.height)
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
If you look at the Coordinate System in iOS, you can see that Frame should work like that.
However, Bounds does not move itself, but only the Subview below it. I do not understand this part.
import UIKit
class ViewController: UIViewController {
var childView : UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let rect = CGRect(x: 100, y: 100, width: 200, height: 200)
childView = UIView(frame: rect)
childView.backgroundColor = UIColor.red
self.view.addSubview(childView)
self.view.backgroundColor = UIColor.cyan
}
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 10, animations: {
self.view.bounds = CGRect(x: 0, y: 700, width: self.view.frame.size.width, height: self.view.frame.size.height)
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
So, I searched it in google.
Eventually, I found a mathematical formula for "SubView" to reposition when SuperView.origin.bounds was changed.
( 출처 : article )
CompositedPosition.x = View.frame.origin.x - Superview.bounds.origin.x;
CompositedPosition.y = View.frame.origin.y - Superview.bounds.origin.y;
By the way, why did Apple use these formulas? Basically, based on the "Coordinate System" we think,
CompositedPosition.x = View.frame.origin.x + Superview.bounds.origin.x;
CompositedPosition.y = View.frame.origin.y + Superview.bounds.origin.y;
Is not this formula more intuitive?
So, My Question are these.
When I increase SuperView.bounds.origin.y by 700 in its original state, why does SubView move up not moving down ???
Should I just accept the formula and memorize it?
---------------------------------------------------------------
----------------------------Edit--1----------------------------
Now I Got the concept of bounds by studying UIScrollView!
CompositedPosition.x = View.frame.origin.x - Superview.bounds.origin.x;
CompositedPosition.y = View.frame.origin.y - Superview.bounds.origin.y;
This is right.
This is a picture what I understand.
yes, I agree this picture looks dizzy sorry!
If I scroll UP the scrollview, and bounds.origin.y will increase and offset.y will increase and the subviews attached in scrollview's frame.origin.y will not change but, iOS calculate where the subviews to draw(CompositedPosition) by using that formula so! It looks like that the subviews goes UP!.
In brief, bounds change -> iOS calculate by using that formula -> draw!
First, please post in English or another supported language.
Second I think I might have understand what you are going for and here is the general simple answer.
First there is a coordinate system for everything, but each view also has its own coordinated system. Each subview of a view is defined inside of the superviews coordinate system.
I would try and say more but I am purely going of the pictures. And also even in the post was in English, I have trouble believing this post is a minimal example of the presented problem

How to place a subview in the center of parent view using .center?

I have two subviews: the purple one belongs to a main view and it works wonderful with the syntax:
sSubview.center = view.center
The orange one belongs to the purple view and the same syntax doesn't place it correctly in the center.
Could you see where my mistake is?
Here is my code:
import UIKit
class ViewController: UIViewController {
var sSubview = UIView()
var sLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.backgroundColor = UIColor.lightGray
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidLayoutSubviews() {
createSimplestSubview()
}
// First subview
func createSimplestSubview() {
sSubview = UIView(frame: CGRect(x: 0, y: 0, width: view.frame.width * 0.9, height: view.frame.height * 0.9))
sSubview.backgroundColor = UIColor.purple // just for test, to make it visible
sSubview.center = view.center // that s pretty easy!
view.addSubview(sSubview)
createSimplestLabel()
}
// Second subview
func createSimplestLabel() {
sLabel = UILabel(frame: CGRect(x: 0, y: 0, width: sSubview.frame.width * 0.3, height: sSubview.frame.height * 0.2))
sLabel.backgroundColor = UIColor.orange // just for test, to make it visible
sLabel.center = sSubview.center
sLabel.text = "This label has to be centered!"
sLabel.textAlignment = .center
sLabel.numberOfLines = 0
sSubview.addSubview(sLabel)
}
}
Here is a screen:
Try the next code for seconds subview:
sLabel.center = CGPoint(x: sSubview.frame.width / 2, y: sSubview.frame.height / 2)
You're trying to place a label in the center, but it calculated according to the firstSbuview frame, which origin is not 0.0
A better way is to use convertPoint as the problem with your code is that the coordinate spaces are sometimes different.
sSubview.center = view.superview.convert(view.center, to: sSubview.superview)
view.center is in the coordinate space of view.superview
sSubview.center is in the coordinate space of sSubview.superview

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.

SFSafariViewController NOT fullscreen / content presented on top

I'm making a very simple app for a demo and am trying to present a webpage using SFSafariViewController (I need to use SF versus WKWebView so to be able to access cookies).
I would really like to present the User with some UI buttons, but I've been unable to pull it off.
I tried this snippet (placed in the completion callback of presentViewController():
let width: CGFloat = 66
let x: CGFloat = self.view.frame.width - width
// It can be any overlay. May be your logo image here inside an imageView.
let overlay = UIView(frame: CGRect(x: x, y: 20, width: width, height: 44))
overlay.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.5)
svc.view.addSubview(overlay)
... outlined in this post. In their case, they're attempting to cover the reload button with a small view. Regardless of the use-case, for me the view immediately disappears when I load SFSafariViewController (I can see it for a moment and it disappears).
I was thinking about presenting the button in an .OverContext modal, but then the User would be unable to interact with the SFSafariViewController, which also doesn't work.
Here's essentially what I'm after (pardon the gross, quick mockup) ... basically, SafariViewController with a view presented over it (see bottom) ... the transparency is just to show that it's being presented over Safari).
Any recommendations are greatly appreciated.
Figured it out ... there's likely some slight race condition going on that was preventing the recommended "draw a rectangle" code from working as desired. What I did:
Subclassed SFSafariWebViewController
In viewDidAppear, implemented a slight delay using NSTimer that draws any additional view elements
This also ended up helping me hide the status bar, which was giving me issues (see mockup).
Here's the relevant code:
import UIKit
import SafariServices
class MySafariVC: SFSafariViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
var frame = self.view.frame
let OffsetY: CGFloat = 44
frame.origin = CGPoint(x: frame.origin.x, y: frame.origin.y - OffsetY)
frame.size = CGSize(width: frame.width, height: frame.height + (1 * OffsetY))
self.view.frame = frame
NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "drawNavBar", userInfo: nil, repeats: false)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("i laid out my subviews")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prefersStatusBarHidden() -> Bool {
return true
}
func drawNavBar() {
let height: CGFloat = 44
let y: CGFloat = self.view.frame.height - height
// It can be any overlay. May be your logo image here inside an imageView.
let overlay = UIView(frame: CGRect(x: 0, y: y, width: self.view.frame.width, height: height))
overlay.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.9)
self.view.addSubview(overlay)
}
}

Resources