How do i get the vertical distance from an UIView to another one? - ios

As the title suggests, i struggle to find a way to calculate the vertical distance between UIViews.
Say i have two buttons in a View Controller. How would I go about getting a CGFloat indicative of the distance between button1's bottom and button2's top?

As already pointed out by #lobstah, the difference can be calculated by accessing the frames of the views in question.
However, there's an important detail that must be considered: This only works in that simple way, if the views share the same parent view. Because only then, the frames will be expressed with regards to the same coordinate space.
A general approach with doesn't come with this restriction and will always work as long as the views are part of the same view hierarchy (don't appear on different windows) and are not scaled / rotated by an affine transform is the following:
func distanceBetween(bottomOf view1: UIView, andTopOf view2: UIView) -> CGFloat {
let frame2 = view1.convert(view2.bounds, from: view2)
return frame2.minY - view1.bounds.maxY
}
The distance would be positive if the top of view2 is below the bottom of view1.

You can use maxY of the top and minY of bottom view’s frames properties:
bottomView.frame.minY - topView.frame.maxY

Related

Swift 3: Get distance of button to bottom of screen

I want to get the distance of a button to the bottom of the screen in Swift 3.
I can see the correct value in the Storyboard when I look at the distance (alt key) but unfortunately I am not able to calculate it manually.
The value I am looking for is the same as the constant in the "Vertical Spacing to Bottom Layout" constraint for the button.
view.frame.maxY - randomButton.frame.maxY
gave me a value way too high.
view.frame.size.height - (button.frame.size.height + button.frame.origin.y)
I think its ok! Hope it helps
If your button is not a direct successor of the view controller's view (aka, the hierarchy is something like ViewController's View -> SomeOtherView->Button), you won't get the right math by simply using frames. You will have to translate the button's Y-position to the coordinate space of the window object or your view controller's main view.
Take a look at this question: Convert a UIView origin point to its Window coordinate system
let realOrigin = someView.convert(button.frame.origin, to: self.view)
Then apply the math suggested by Lucas Palaian.
let bottomSpace = view.frame.maxY - (button.frame.height + realOrigin.y)
Another work around, in case something really wild and wierd is going on there, is to drag and drop an outlet of the button's bottom constraint. (Select the constraint from the view hierarchy in Interface Builder, hold the control key and drag the constraint to your view controller.) Then in your code you can access the constant.
let bottomSpace = myButtonBottomConstraint.constant
Use this to have it from the bottom of the button to the bottom of the view (Tested):
view.frame.size.height - randomButton.frame.size.height/2 - randomButton.frame.origin.y
I needed to find the distance from the bottom edge of the UICollectionView
to the bottom edge of the screen.
This code works for me:
#IBOutlet weak var collectionView: UICollectionView!
private var bottomSpace = CGFloat()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
bottomSpace = UIScreen.main.bounds.height - collectionView.frame.maxY
}
This method is called several times it gives the correct result.

UIScrollView with Horizontal Zoom - Bezier Paths Drawn within Subviews Are Blurry

I have a scroll view that zooms only horizontally on pinch (based on this answer)
The content view has several children that are evenly spaced horizontally, and placed at different heights. Basically, I'm plotting a graph with dots.
Each marker is a custom UIView subclass that has a label as subview and draws a red circle inside drawRect(_:).
(The scroll view and its only child the content view use Autolayout, but all subviews of the content view are placed with frames calculated at runtime; no constraints except the label positioning respect to the marker)
I have modified the answer linked above, so that -when zooming- the dots get more spaced horizontally, but stay the same size.
This is part of the code for my content view:
override var transform: CGAffineTransform {
get {
return super.transform
}
set {
if let unzoomedViewHeight = unzoomedViewHeight {
// ^Initial height of the content view, captured
// on layoutSubviews() and used to calculate a
// a zoom transform that preserves the height
// (see linked answer for details)
// 1. Ignore vertical zooming for this view:
var modified = newValue
modified.d = 1.0
modified.ty = (1.0 - modified.a) * unzoomedViewHeight/2
super.transform = modified
// 2. Ignore zooming altogether in marker subviews:
var markerTransform = CGAffineTransformInvert(newValue)
markerTransform.d = 1.0 // No y-scale
for subview in subviews where subview is MarkerView {
subview.transform = markerTransform
}
}
}
}
Next, I want to connect my dots with straight line segments. The problem is, when I zoom the scroll view the segments become blurry.
In addition to the blurring, and because I am only zooming horizontally, the segments "shear" a bit (depending on their their slope), and thus the line width becomes uneven:
(Right now I am placing an intermediate view between each pair of dots, that draws a single segment - but the result is the same with a single path)
I have tried making the segment-drawing views aware of the transform that is applied to them by their parent, revert it, and instead modify their frame accordingly (and redraw the path within the new bounds); however it doesn't seem to work.
What is the best way to draw zoom-resistant bezier paths inside a scroll view?
Update:
I got rid of the pixelation by following this answer to a similar question (almost duplicate?); in my case the code translates to:
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat)
{
for segment in contentView.subviews where segment is SegmentView {
segment.layer.contentsScale = scale
segment.setNeedsDisplay()
}
}
...However, I still have the uneven line width issue due to stretching the (diagonal) segments only in the horizontal direction.
My initial thought, having not done this before exactly, is to store the graph as a bezier path rather than as a set of view line and point views, and based on a single unit axis. You could convert your data into this form just for drawing.
So, all points on the bezier path are normalised into the range 0 to 1.
Once you've done that you can apply a transform to the bezier path to translate (move) and scale (zoom) to the part you want and then draw the bezier path at full resolution.
This will work and is different to your current situation because your current code draws the views and then scales then so you get artifacts. The above option scales the path and then draws it.

Changing view's alpha based on UIScrollView's contentOffset.x when scrolling multiple screens

I have a horizontal UIScrollView that has a width of 960.
The UIScrollView contains 3 UIView's, each one set to a background color.
The first one is pink, the second blue, and the third is green.
What I need to do is change the alpha of the colors(views) based on the user scrolling.
So if the user is on the first screen (pink) and starts to scroll to the second page (blue), then the pink should start to fade and the blue will become more visible, and once the user fully swipes to that 2nd page it would be all blue.
Here is how I do this when using only one UIView:
func scrollViewDidScroll(scrollView: UIScrollView) {
// Offset Percentage
var percentageHorizontalOffset = scrollView.contentOffset.x / 320;
// Reduce alpha
pinkView.alpha = percentageHorizontalOffset
}
This is easy because the percentage goes from 0.0 to 1.0
However, I need to modify the code to support 3 screens. I tried this earlier by replacing 320 with 960 in the method above but this causes a couple problems.
The first is that you are no longer getting a percentage in the range of 0.0 to 1.0, once you scroll on the second page it will be a range of 1.0 to 2.0 which won't help me properly modify the alpha.
The second is that the alpha change doesn't feel very smooth, especially when the UIScrollView is swiped quickly. I setup a mess of if statements earlier trying to get this to work but nothing worked.
How can I properly fade the alpha of all 3 UIView's based on the contentOffset.x when my UIScrollView's content size is bigger than just one screen?
Simplest way would be to treat the 3 colored views as an array.
// Put all your views in an array
#property NSArray *coloredViews;
// Later where you set things up, allocate the array.
coloredViews=#[pinkView,blueView,greenView];
Then use the following algorithm to set the alpha.
This assumes only 2 of N views on screen at a time. One on left and one on the right.
The alpha of the left is based on the amount of it off screen.
The alpha of the right is based on the amount of it on screen.
So as left goes off it goes to 0 causing right to go to 1 and vice versa.
This makes your scroll method:
func scrollViewDidScroll(scrollView: UIScrollView) {
// Work out which view is showing on the left. 0 to N-1
// Based on each being 320 wide.
CGFloat offset=scrollView.contentOffset.x;
NSUInteger leftHandSideViewIndex=(NSUInteger)offset.x/320;
// Left hand side alpha is 1 minus percentage (as a fraction) off screen
self.coloredViews[leftHandSide].alpha =
1.0 - (offset - (320.0 * leftHandSideViewIndex))/320.0;
// Right hand side alpha is 1 - percentage (as a fraction) on screen
// Only do this if there is a right hand side.
if (leftHandSideViewIndex < self.coloredViews.count-1){
self.coloredViews[leftHandSide+1].alpha =
1.0 - ((320.0 * leftHandSizeViewIndex+1)-offset)/320.0;
}
}
Give it a try. Might not be 100% right, but hopefully gives you the idea.You probably want to set a width variable rather than using 320.0.

How can I horizontally center two side-by-side UILabels?

I have a series of labels that I want to center horizontally (that is, along the X axis), like this:
thing1: value1
thing2: value2
etc.
Autolayout doesn't seem to allow this. Even if I stick the two together and use the guidelines to center the pair, the center constraint only applies to the first label in the pair.
The various "things" will have different lengths, and I want to colons to line up (right-justified) and the values on the left to line up (left-justified).
Is there any way to do this?
This is easy to do in code. The x-offset of the first label should be half the difference between the width of the parent view and the sum of the widths of the two labels. For example:
CGFloat xOffset = ((parentView.frame.size.width -
(view1.frame.size.width + view2.frame.size.width)) / 2);
// now position the first label using this offset
view1.frame = CGRectMake(xOffset,
view1.frame.origin.y,
view1.frame.size.width,
view1.frame.size.height);
// and position the second view relative to the first
view2.frame = CGRectMake(CGRectGetMaxX(view1.frame),
view2.frame.origin.y,
view2.frame.size.width,
view2.frame.size.height);

How can I tap on a UICollectionView cell of a collection view in UIAutomation?

How can I tap on a UICollectionView cell of a collection view in UIAutomation?
I tried this
var iconsCollView = window.collectionViews()[0];
var iconRect = iconsCollView.cells()[0].rect();
var iconX = iconRect.origin.x/100;
var iconY = iconRect.origin.y/100;
iconsCollView.tapWithOptions({tapOffset:{x: iconX, y: iconY}});
but it taps another cell in the collection view, a wrong cell other than the cell I specified its offset.
Can you please help me? is there another way?
From the UIAElement class reference:
Your script should treat the rect object as a generic JavaScript object whose properties for origin, x, y, size, width, and height correspond to those of the analogous CGRect Cocoa structure. The rect object has the form {origin:{x:xposition,y:yposition}, size:{width:widthvalue, height:heightvalue}}. The relevant coordinates are screen-relative and are adjusted to account for device orientation.
From the same source under tapWithOptions method:
You can use offsets to achieve finer precision in specifying the hitpoint within the rect for the specified element. The offset comprises a pair of x and y values, each ranging from 0.0 to 1.0. These values represent, respectively, relative horizontal and vertical positions within the rect, with {x:0.0, y:0.0} as the top left and {x:1.0, y:1.0} as the bottom right. Thus, {x:0.3, y:0.6} specifies a position just below and to the left of center, and {x:1.0, y:0.5} specifies a position centered vertically at the far right.
From the source you provided, you're trying to tap inside of collection view passing some weirdly scaled offset coordinates of one of its cells (instead of expected relative horizontal and vertical positions within the rect of collection view).
If you want to tap the cell simply locate it and call the tap method on it:
var iconsCollView = window.collectionViews()[0];
var iconCellToTap = iconsCollView.cells()[0].tap();

Resources