Get position of UIView in respect to its superview's superview - iphone

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"?

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

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)

Updated for Swift 3
if let frame = yourViewName.superview?.convert(yourViewName.frame, to: nil) {
print(frame)
}

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)

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;

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.

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;

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

Related

Determine if view reached the top of the UIScrollView

With the image above, I would like to know/detect if the UIView 3 has reached the top of the UIScrollView.
What I did so far is to get the offSet of the UIView within the UIScrollView then check if it's less than or equal to 0 (zero).
let offsetY = scrollView.convert(myView.frame, to: nil).origin.y
if offsetY <= 0 {
print("myView is at Top")
}
But the code above is surely wrong! anybody who has an idea?
TIA!
Just convert the top of the scroll view and the top of v3 to the same coordinate system and now you can simply look to see which one is higher.
Let sv be the scroll view and v3 be view 3. Then:
func scrollViewDidScroll(_ sv: UIScrollView) {
let svtop = sv.frame.origin.y
let v3top = sv.superview!.convert(v3.bounds.origin, from:v3).y
if v3top < svtop { print("now") }
}
I'm actually not seeing anything wrong with your approach. Since according to the documentation, when view is nil, the method converts to window based coordinates:
Parameters
rect
A rectangle specified in the local coordinate system (bounds) of the receiver.
view
The view that is the target of the conversion operation. If view
is nil, this method instead converts to window base coordinates.
Otherwise, both view and the receiver must belong to the same
UIWindow object.
Another option if you don't want to assume the scroll view has a superview.
func scrollViewDidScroll(_ sv: UIScrollView) {
// The view we want to check if it's at the top of the scroll view
//let viewThatMayBeAtTop = UIView()
// get how far in the scroll view this view is
let yInScrollViewWithoutScroll = sv.convert(viewThatMayBeAtTop.bounds, from: viewThatMayBeAtTop).minY
// How far we've scrolled down
let scrolledAmount = sv.adjustedContentInset.top + sv.contentOffset.y
// If how far we've scrolled is equal or greater than the y position of the view, then it's at the top or higher
let isScrolledToTopOrHigher = scrolledAmount >= yInScrollViewWithoutScroll
if isScrolledToTopOrHigher {
print("now")
}
}

Detect if label touches navigation bar

I am trying to make view that is scrollable. That viewcontroller contains also navbar. Now my goal is to resize my view if the title in that view touches the navbar. How should I do it?
This is how my view looks like(note that the navBar is just transparent):
What I want after the title collision:
I know that I can achieve it in scrollViewDidScroll delegate function, but how?
Well you can track label position using convertRect: method as
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let labelTop = label.rectCorrespondingToWindow.minY
let navBottom = self.navigationController?.navigationBar.rectCorrespondingToWindow.maxY
if navBottom == labelTop {
// do what you want to do
}
}
extension UIView{
var rectCorrespondingToWindow:CGRect{
return self.convert(self.bounds, to: nil)
}
}

check if UIView is in UIScrollView visible state

What is the easiest and most elegant way to check if a UIView is visible on the current UIScrollView's contentView? There are two ways to do this, one is involving the contentOffset.y position of the UIScrollView and the other way is to convert the rect area?
If you're trying to work out if a view has been scrolled on screen, try this:
CGRect thePosition = myView.frame;
CGRect container = CGRectMake(scrollView.contentOffset.x, scrollView.contentOffset.y, scrollView.frame.size.width, scrollView.frame.size.height);
if(CGRectIntersectsRect(thePosition, container))
{
// This view has been scrolled on screen
}
Swift 5: in case that you want to trigger an event that checks that the entire UIView is visible in the scroll view:
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.bounds.contains(targetView.frame) {
// entire UIView is visible in scroll view
}
}
}
Implement scrollViewDidScroll: in your scroll view delegate and calculate manually which views are visible (e.g. by checking if CGRectIntersectsRect(scrollView.bounds, subview.frame) returns true.
updated for swift 3
var rect1: CGRect!
// initialize rect1 to the relevant subview
if rect1.frame.intersects(CGRect(origin: scrollView.contentOffset, size: scrollView.frame.size)) {
// the view is visible
}
I think your ideas are correct. if it was me i would do it as following:
//scrollView is the main scroll view
//mainview is scrollview.superview
//view is the view inside the scroll view
CGRect viewRect = view.frame;
CGRect mainRect = mainView.frame;
if(CGRectIntersectsRect(mainRect, viewRect))
{
//view is visible
}
José's solution didn't quite work for me, it was detecting my view before it came on screen. The following intersects code works perfect in my tableview if José's simpler solution doesn't work for you.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let viewFrame = scrollView.convert(targetView.bounds, from: targetView)
if viewFrame.intersects(scrollView.bounds) {
// targetView is visible
}
else {
// targetView is not visible
}
}
Solution that takes into account insets
public extension UIScrollView {
/// Returns `adjustedContentInset` on iOS >= 11 and `contentInset` on iOS < 11.
var fullContentInsets: UIEdgeInsets {
if #available(iOS 11.0, *) {
return adjustedContentInset
} else {
return contentInset
}
}
/// Visible content frame. Equal to bounds without insets.
var visibleContentFrame: CGRect {
bounds.inset(by: fullContentInsets)
}
}
if scrollView.visibleContentFrame.contains(view) {
// View is fully visible even if there are overlaying views
}

Getting a UIView's visible rectangle

I have a UIScrollView that contains a custom UIView. Inside the custom UIView, I'd like to know the rectangle in which it's visible (i.e. not clipped).
The quick-n-dirty solution is to have the custom UIView assume that the parent is a UIScrollView and get the content size through it, but I'm looking for a better solution that doesn't involve make such assumptions.
This should do the trick
CGRect visibleRect = CGRectIntersection(self.frame, superview.bounds);
Use that in the UIView and it should get you the rectangle (if any) that represents the visible section of that view in it's superview (The UIScrollView). I'm assuming here that there is no view between them in the hierarchy, but if there is, fiddling the code should be trivial.
Hope I could help!
It would help if you would give more info on what is that you are trying to accomplish.
If you want to know the size of the super view you can do this:
CGRect superFrame = [self superview].frame;
Swift 3
extension UIView {
var visibleRect: CGRect? {
guard let superview = superview else { return nil }
return frame.intersection(superview.bounds)
}
}

UIPickerView: Get row value while spinning?

I'd like to get the row value in real-time as the iPhone user spins a wheel in a UIPickerView (not just when the wheel settles onto a particular row). I looked into subclassing UIPickerView then overriding the mouseDown method, but I couldn't get this to work. Any suggestions would be very much appreciated.
Perhaps try implementing the delegate method:
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view
You could treat it as a passthrough (just passing back the reusingView parameter) but each time it was called you would know that view was coming on the screen as the user scrolled - then you could calculate how many views offset from this one the center view was.
The UIPickerView dimensions are fairly consistent. Instead of subclassing it, perhaps you could overlay a UIView of your own on top of the picker view, from which you can track and measure dragging motions, before passing those touches down to the picker view.
You can find UIScrollView in UIPickerView hierarchy by the following method:
func findScrollView(view:UIView) -> UIScrollView? {
if view is UIScrollView {
return view as? UIScrollView
}
for subview in view.subviews {
if subview is UIView {
let result = findScrollView(subview as UIView)
if result != nil {
return result
}
}
}
return nil
}
Implement and setup UIScrollViewDelegate:
let scrollView = findScrollView(pickerView)
if scrollView != nil {
scrollView!.delegate = self
}
And detect current selected item:
func scrollViewDidScroll(scrollView: UIScrollView) {
let offset = scrollView.contentOffset.y
let index = Int(offset/itemHeight)
if index >= 0 && index < items.count {
let item = items[index]
// do something with item
}
}