Reputation: 3118
I have a UIView, in which I have arranged UIButtons. I want to find the positions of those UIButtons.
I am aware that buttons.frame
will give me the positions, but it will give me positions only with respect to its immediate superview.
Is there is any way we can find the positions of those buttons, withe respect to UIButtons superview's superview?
For instance, suppose there is UIView named "firstView".
Then, I have another UIView, "secondView". This "SecondView" is a subview of "firstView".
Then I have UIButton as a subview on the "secondView".
->UIViewController.view
--->FirstView A
------->SecondView B
------------>Button
Now, is there any way we can find the position of that UIButton, with respect to "firstView"?
Upvotes: 120
Views: 111770
Reputation: 34175
iOS converting between view coordinate systems
Our UI has a tree structure that is why every level uses its own coordinate system
UIView -> CALayer with bounds and frame
UIView.bounds
x and y - it is a start point in it's local coordinate system(bounds), usually but not always it is (0, 0)
UIView.frame
x and y - it is a start point in parent (superview) coordinate system
You can use 1 of 4 methods to convert Point or Rectangle from one coordinate system to another
Take a look into example with viewA has viewB, viewB has viewC
:
Let's take a look at convert method for Point. It has two interpretation which are equal by result
Convert point from viewC local coordinate system(bounds) to viewA coordinate system
let convertTo = viewC.convert(CGPoint(x: 1, y: 1), to: viewA)
print("convertTo: \(convertTo)") //convertTo: (101.0, 101.0)
let convertFrom = viewA.convert(CGPoint(x: 1, y: 1), from: viewC)
print("convertFrom: \(convertFrom)") //convertFrom: (101.0, 101.0)
Please note that .convert(CGRect)
convert Rect from local coordinate system into parent coordinate system. For example when you work with rotated view it can be helpful
Local coordinate system(bounds). It is important to note that you work with bounds. For example you rotate viewC on 45 degrees
source code:
print("viewA frame: \(viewA.frame)") //viewA frame: (0.0, 0.0, 200.0, 200.0)
print("viewA bounds: \(viewA.bounds)") //viewA bounds: (0.0, 0.0, 200.0, 200.0)
print("viewB frame: \(viewB.frame)") //viewB frame: (100.0, 100.0, 100.0, 100.0)
print("viewB bounds: \(viewB.bounds)") //viewB bounds: (0.0, 0.0, 100.0, 100.0)
print("viewC frame: \(viewC.frame)") //viewC frame: (0.0, 0.0, 50.0, 50.0)
print("viewC bounds: \(viewC.bounds)") //viewC bounds: (0.0, 0.0, 50.0, 50.0)
//convert To
// read as convert point from viewC coordinate system to viewA coordinate system
let convertTo = viewC.convert(CGPoint(x: 1, y: 1), to: viewA)
print("convertTo: \(convertTo)") //convertTo: (101.0, 101.0)
//convert From
// read as convert point from viewC coordinate system to viewA coordinate system
let convertFrom = viewA.convert(CGPoint(x: 1, y: 1), from: viewC)
print("convertFrom: \(convertFrom)") //convertFrom: (101.0, 101.0)
//convert Rect
let convertToRect = viewC.convert(CGRect(x: 1, y: 1, width: 5, height: 5), to: viewA)
print("convertToRect: \(convertToRect)") //convertToRect: (101.0, 101.0, 5.0, 5.0)
//transform
viewC.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 4)
print("viewC frame: \(viewC.frame)") //viewC frame: (-10.355339059327374, -10.355339059327378, 70.71067811865476, 70.71067811865476)
print("viewC bounds: \(viewC.bounds)") //viewC bounds: (0.0, 0.0, 50.0, 50.0)
let convertTo2 = viewC.convert(CGPoint(x: 0, y: 0), to: viewA)
print("convertTo2: \(convertTo2)") //(125.0, 89.64466094067262)
Upvotes: 0
Reputation: 882
UIView extension for converting subview's frame (inspired by @Rexb answer).
extension UIView {
// there can be other views between `subview` and `self`
func getConvertedFrame(fromSubview subview: UIView) -> CGRect? {
// check if `subview` is a subview of self
guard subview.isDescendant(of: self) else {
return nil
}
var frame = subview.frame
if subview.superview == nil {
return frame
}
var superview = subview.superview
while superview != self {
frame = superview!.convert(frame, to: superview!.superview)
if superview!.superview == nil {
break
} else {
superview = superview!.superview
}
}
return superview!.convert(frame, to: self)
}
}
// usage:
let frame = firstView.getConvertedFrame(fromSubview: buttonView)
Upvotes: 15
Reputation: 10738
Please note that the following code works regardless of how deep (nested) is the view you need the location for in the view hierarchy.
Let's assume that you need the location of myView
which is inside myViewsContainer
and which is really deep in the view hierarchy, just follow the following sequence.
let myViewLocation = myViewsContainer.convert(myView.center, to: self.view)
myViewsContainer: the view container where the view you need the location for is located.
myView: the view you need the location for.
self.view : the main view
Upvotes: 2
Reputation: 21902
You can use this:
Objective-C
CGRect frame = [firstView convertRect:buttons.frame fromView:secondView];
Swift
let frame = firstView.convert(buttons.frame, from:secondView)
Documentation reference:
https://developer.apple.com/documentation/uikit/uiview/1622498-convert
Upvotes: 232
Reputation: 445
If there are multiple views stacked and you don't need (or want) to know any possible views between the views you are interested in, you could do this:
static func getConvertedPoint(_ targetView: UIView, baseView: UIView)->CGPoint{
var pnt = targetView.frame.origin
if nil == targetView.superview{
return pnt
}
var superView = targetView.superview
while superView != baseView{
pnt = superView!.convert(pnt, to: superView!.superview)
if nil == superView!.superview{
break
}else{
superView = superView!.superview
}
}
return superView!.convert(pnt, to: baseView)
}
where targetView
would be the Button, baseView
would be the ViewController.view
.
What this function is trying to do is the following:
If targetView
has no super view, it's current coordinate is returned.
If targetView's
super view is not the baseView (i.e. there are other views between the button and viewcontroller.view
), a converted coordinate is retrieved and passed to the next superview
.
It continues to do the same through the stack of views moving towards the baseView.
Once it reaches the baseView
, it does one last conversion and returns it.
Note: it doesn't handle a situation where targetView
is positioned under the baseView
.
Upvotes: 5
Reputation: 139
You can easily get the super-super view as following.
secondView -> [button superview]
firstView -> [[button superview] superview]
Then, You can get the position of the button..
You can use this:
xPosition = [[button superview] superview].frame.origin.x;
yPosition = [[button superview] superview].frame.origin.y;
Upvotes: 3
Reputation: 5927
Although not specific to the button in the hierarchy as asked I found this easier to visualize and understand:
From here: original source
ObjC:
CGPoint point = [subview1 convertPoint:subview2.frame.origin toView:viewController.view];
Swift:
let point = subview1.convert(subview2.frame.origin, to: viewControll.view)
Upvotes: 62
Reputation: 3817
Updated for Swift 3
if let frame = yourViewName.superview?.convert(yourViewName.frame, to: nil) {
print(frame)
}
Upvotes: 25
Reputation: 1819
Frame: (X,Y,width,height).
Hence width and height wont change even wrt the super-super view. You can easily get the X, Y as following.
X = button.frame.origin.x + [button superview].frame.origin.x;
Y = button.frame.origin.y + [button superview].frame.origin.y;
Upvotes: 8