UIScrollview zoomToRect center of currently visible view - ios

I have a UIScrollView displaying a image. I want to programmatically zoom in to the a rect somewhere near the center (doesn't have to be exact) of the currently visible area. How would I get the coordinates of this rect to use with zoomToRect? Note that this image could be already zoomed in and only showing a fraction of the scrollView content area.

The the X and Y position of that image are relative to the scrollview's contentSize. The area shown on screen is defined by the scrollview's contentOffset.
You then take the position of your scrollview on screen and the position of the selection rectangle on your screen.
Finally you need to do rather simple maths (a few additions/subtractions) for both X and Y using the above values.

Grab UIImageView's frame and call insetBy(dx:dy:):
Returns a rectangle that is smaller or larger than the source
rectangle, with the same center point.
From Apple Documentation
Here's a quick visualisation in a PlayGround:
let blueSquare = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
let yellowSquare = UIView(frame: blueSquare.frame.insetBy(dx: 100, dy: 100))
blueSquare.backgroundColor = .blue
yellowSquare.backgroundColor = .yellow
blueSquare.addSubview(yellowSquare)
Will result in this:

Related

How to specify position that popover controller's arrow points to

It's well known that in order to present UIAlertController with preferredStyle: .actionSheet, it's a good practice to set an anchor using the next code:
popover.sourceView = view
popover.sourceRect = view.bounds
In my case, the view is UIImageView, it's size is 44x44, but its image (three vertical dots) is very thin (its size is 4x24). So the view is mostly transparent. As a result, popover "points" to empty space. (Note that I am using red color to show fully transparent area)
Ideally, popover's arrow should point to the central white dot. But I do not know how to achieve it. I tried to modify sourceRect as follows:
popover.sourceRect = CGRect(x: view.bounds.midX - 1,
y: view.bounds.midY - 1,
width: 2,
height: 2)
but it points to the top white dot for some reason:
How can I make it pointing to the desired point?
The sourceRect worked for me, i just used the y coordinates to position the arrow and it worked for me.
popover.sourceRect = CGRect(x: 0, y: yourPosition, width: 0, height: 0)
You can also have a look at this.

Changing origin point of an animation of a UIImage in Swift3

I have the following code, which basically just grows a UIImage from 0 to its intended height:
let bounds = self.tankLevelRep.bounds
UIView.transition(with: self.tankLevelRep, duration: 2.0, animations: {self.tankLevelRep.bounds = CGRect(x: bounds.origin.x, y: bounds.origin.y, width: bounds.size.width, height: 118.0)}, completion: nil)
This works fine except for the fact that it grows from the middle. I would like it to grow upwards from the bottom. The attached graphic shows the animation as it nears its end. You can see that the animation is not starting from the bottom, but rather, from the center, and growing outwards. I would like for it to start at the bottom, and the image to grow upwards.
What can I change in my code to make this happen?
Notes:
• "tankLevelRep" is the outlet name for my UIImage
You should update the frame and the bounds mirror the change, in your code make sure you animate from the bottom by setting the origin to bounds.size.height instead of the origin.y. Here is a nice fill animation from another post.

Merging two scrolled and zoomed images in Swift

I try to create a functionallity like in PicFrame or any other application for creating photo collage in one frame.
I've created two scroll views and two image views in these scroll views for scrolling and zooming the images. It works well.
Then I need to create one square image of the two rectangular images.
var firstImage = UIImage(named: leftImagePath)
var secondImage = UIImage(named: rightImagePath)
var size = CGSize(width: 1080, height: 1080)
UIGraphicsBeginImageContext(size)
let leftImageAreaSize = CGRect(x: 0, y: 0, width: size.width / 2, height: size.height)
firstImage!.drawInRect(leftImageAreaSize)
let rightImageAreaSize = CGRect(x: size.width / 2 + 1 , y: 0, width: size.width / 2, height: size.height)
secondImage!.drawInRect(rightImageAreaSize)
var newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
This code works well, but I need to implement the scroll and zoom values, crop and scale the images using these values before creating one square image.
Can anyone guide me how to do this?
I make PicFrame so I suppose I have some experience here. Although this isn't what I do, and I haven't tried it myself, if you just want a quick image of what you see you could use drawViewHierarchyInRect and capture the screen area.
Otherwise what you want to do is get the CGPoint contentOffset and the CGSize bounds.size from the UIScrollView. Then you modify these by the UIScrollView zoomScale. Make sure your contentSize is the size of the image so that a zoomScale of 1.0 would have the width and height of the original image.
From this you should be able to make a CGRect which is the x, y, width, height of what is visible in your scroll view but translated to the size of the image. Crop the image to this size and then draw it into your final graphics context with your desired CGRect.

How to set a UIView's origin reference?

I am creating a UIImageView and adding it in a loop to my view, I set the initial frame to 0,0,1,47 and each passage of the loop I change the center of the image view to space them out.
I am always using 0 as the origin.y
The problem is the origin reference is in the centre of the image view, assuming we was in interface builder, this is equivalent to the image below.
How can I change the reference point in code ?
After reading these answers and your comments I'm not really sure what is your point.
With UIView you can set position by 2 ways:
center – It definitely says it is the center.
frame.origin – Top left corner, can't be set directly.
If you want the bottom left corner to be at x=300, y=300 you can just do this:
UIView *view = ...
CGRect frame = view.frame;
frame.origin.x = 300 - frame.size.width;
frame.origin.y = 300 - frame.size.height;
view.frame = frame;
But if you go one level deeper to magical world of CALayers (don' forget to import QuartzCore), you are more powerful.
CALayer has these:
position – You see, it don't explicitely says 'center', so it may not be center!
anchorPoint – CGPoint with values in range 0..1 (including) that specifies point inside the view. Default is x=0.5, y=0.5 which means 'center' (and -[UIView center] assumes this value). You may set it to any other value and the position property will be applied to that point.
Example time:
You have a view with size 100x100
view.layer.anchorPoint = CGPointMake(1, 1);
view.layer.position = CGPointMake(300, 300);
Top left corner of the view is at x=200, y=200 and its bottom right corner is at x=300, y=300.
Note: When you rotate the layer/view it will be rotated around the anchorPoint, that is the center by default.
Bu since you just ask HOW to do specific thing and not WHAT you want to achieve, I can't help you any further now.
The object's frame includes its position in its superview. You can change it with something like:
CGRect frame = self.imageView.frame;
frame.origin.y = 0.0f;
self.imageView.frame = frame;
If I am understanding you correctly, you need to set the frame of the image view you are interested in moving. This can be done in the simple case like this:
_theImageView.frame = CGRectMake(x, y, width, height);
Obviously you need to set x, y, width, and height yourself. Please also be aware that a view's frame is in reference to its parent view. So, if you have a view that is in the top left corner (x = 0, y = 0), and is 320 points wide and 400 points tall, and you set the frame of the image view to be (10, 50, 100, 50) and then add it as a subview of the previous view, it will sit at x = 10, y = 50 of the parent view's coordinate space, even though the bounds of the image view are x = 0, y = 0. Bounds are in reference to the view itself, frame is in reference to the parent.
So, in your scenario, your code might look something like the following:
CGRect currentFrame = _theImageView.frame;
currentFrame.origin.x = 0;
currentFrame.origin.y = 0;
_theImageView.frame = currentFrame;
[_parentView addSubview:_theImageView];
Alternatively, you can say:
CGRect currentFrame = _theImageView.frame;
_theImageView.frame = CGRectMake(0, 0, currentFrame.size.width, currentFrame.size.height);
[_parentView addSubview:_theImageView];
Either approach will set the image view to the top left of the parent you add it to.
I thought I would take a cut at this in Swift.
If one would like to set a views position on the screen by specifying the coordinates to an origin point in X and Y for that view, with a little math, we can figure out where the center of the view needs to be in order for the origin of the frame to be located as desired.
This extension uses the frame of the view to get the width and height.
The equation to calculate the new center is almost trivial. See the below extension :
extension CGRect {
// Created 12/16/2020 by Michael Kucinski for anyone to reuse as desired
func getCenterWhichPlacesFrameOriginAtSpecified_X_and_Y_Coordinates(x_Position: CGFloat, y_Position: CGFloat) -> CGPoint
{
// self is the CGRect
let widthDividedBy2 = self.width / 2
let heightDividedBy2 = self.height / 2
// Calculate where the center needs to be to place the origin at the specified x and y position
let desiredCenter_X = x_Position + widthDividedBy2
let desiredCenter_Y = y_Position + heightDividedBy2
let calculatedCenter : CGPoint = CGPoint(x: desiredCenter_X, y: desiredCenter_Y)
return calculatedCenter // Using this point as the center will place the origin at the specified X and Y coordinates
}
}
Usage as shown below to place the origin in the upper left corner area, 25 pixels in :
// Set the origin for this object at the values specified
maskChoosingSlider.center = maskChoosingSlider.frame.getCenterWhichPlacesFrameOriginAtSpecified_X_and_Y_Coordinates(x_Position: 25, y_Position: 25)
If you want to pass a CGPoint into the extension instead of X and Y coordinates, that's an easy change you can make on your own.

Understand convertRect:toView:, convertRect:FromView:, convertPoint:toView: and convertPoint:fromView: methods

I'm trying to understand the functionalities of these methods. Could you provide me with a simple use case to understand their semantics?
From the documentation, for example, convertPoint:fromView: method is described as follows:
Converts a point from the coordinate system of a given view to that of the receiver.
What does the coordinate system mean? What about the receiver?
For example, does it make sense using convertPoint:fromView: like the following?
CGPoint p = [view1 convertPoint:view1.center fromView:view1];
Using NSLog utility, I've verified that p value coincides with view1's center.
Thank you in advance.
EDIT: For those interested, I've created a simple code snippet to understand these methods.
UIView* view1 = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 150, 200)];
view1.backgroundColor = [UIColor redColor];
NSLog(#"view1 frame: %#", NSStringFromCGRect(view1.frame));
NSLog(#"view1 center: %#", NSStringFromCGPoint(view1.center));
CGPoint originInWindowCoordinates = [self.window convertPoint:view1.bounds.origin fromView:view1];
NSLog(#"convertPoint:fromView: %#", NSStringFromCGPoint(originInWindowCoordinates));
CGPoint originInView1Coordinates = [self.window convertPoint:view1.frame.origin toView:view1];
NSLog(#"convertPoint:toView: %#", NSStringFromCGPoint(originInView1Coordinates));
In both cases self.window is the receiver. But there is a difference. In the first case the convertPoint parameter is expressed in view1 coordinates. The output is the following:
convertPoint:fromView: {100, 100}
In the second one, instead, convertPoint is expressed in superview (self.window) coordinates. The output is the following:
convertPoint:toView: {0, 0}
Each view has its own coordinate system - with an origin at 0,0 and a width and height. This is described in the bounds rectangle of the view. The frame of the view, however, will have its origin at the point within the bounds rectangle of its superview.
The outermost view of your view hierarchy has it's origin at 0,0 which corresponds to the top left of the screen in iOS.
If you add a subview at 20,30 to this view, then a point at 0,0 in the subview corresponds to a point at 20,30 in the superview. This conversion is what those methods are doing.
Your example above is pointless (no pun intended) since it converts a point from a view to itself, so nothing will happen. You would more commonly find out where some point of a view was in relation to its superview - to test if a view was moving off the screen, for example:
CGPoint originInSuperview = [superview convertPoint:CGPointZero fromView:subview];
The "receiver" is a standard objective-c term for the object that is receiving the message (methods are also known as messages) so in my example here the receiver is superview.
I always find this confusing so I made a playground where you can visually explore what the convert function does. This is done in Swift 3 and Xcode 8.1b:
import UIKit
import PlaygroundSupport
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Main view
view.backgroundColor = .black
view.frame = CGRect(x: 0, y: 0, width: 500, height: 500)
// Red view
let redView = UIView(frame: CGRect(x: 20, y: 20, width: 460, height: 460))
redView.backgroundColor = .red
view.addSubview(redView)
// Blue view
let blueView = UIView(frame: CGRect(x: 20, y: 20, width: 420, height: 420))
blueView.backgroundColor = .blue
redView.addSubview(blueView)
// Orange view
let orangeView = UIView(frame: CGRect(x: 20, y: 20, width: 380, height: 380))
orangeView.backgroundColor = .orange
blueView.addSubview(orangeView)
// Yellow view
let yellowView = UIView(frame: CGRect(x: 20, y: 20, width: 340, height: 100))
yellowView.backgroundColor = .yellow
orangeView.addSubview(yellowView)
// Let's try to convert now
var resultFrame = CGRect.zero
let randomRect: CGRect = CGRect(x: 0, y: 0, width: 100, height: 50)
/*
func convert(CGRect, from: UIView?)
Converts a rectangle from the coordinate system of another view to that of the receiver.
*/
// The following line converts a rectangle (randomRect) from the coordinate system of yellowView to that of self.view:
resultFrame = view.convert(randomRect, from: yellowView)
// Try also one of the following to get a feeling of how it works:
// resultFrame = view.convert(randomRect, from: orangeView)
// resultFrame = view.convert(randomRect, from: redView)
// resultFrame = view.convert(randomRect, from: nil)
/*
func convert(CGRect, to: UIView?)
Converts a rectangle from the receiver’s coordinate system to that of another view.
*/
// The following line converts a rectangle (randomRect) from the coordinate system of yellowView to that of self.view
resultFrame = yellowView.convert(randomRect, to: view)
// Same as what we did above, using "from:"
// resultFrame = view.convert(randomRect, from: yellowView)
// Also try:
// resultFrame = orangeView.convert(randomRect, to: view)
// resultFrame = redView.convert(randomRect, to: view)
// resultFrame = orangeView.convert(randomRect, to: nil)
// Add an overlay with the calculated frame to self.view
let overlay = UIView(frame: resultFrame)
overlay.backgroundColor = UIColor(white: 1.0, alpha: 0.9)
overlay.layer.borderColor = UIColor.black.cgColor
overlay.layer.borderWidth = 1.0
view.addSubview(overlay)
}
}
var ctrl = MyViewController()
PlaygroundPage.current.liveView = ctrl.view
Remember to show the Assistant Editor (⎇⌘⏎) in order to see the views, it should look like this:
Feel free to contribute more examples here or in this gist.
Here's an explanation in plain English.
When you want to convert the rect of a subview (aView is a subview of [aView superview]) to the coordinate space of another view (self).
// So here I want to take some subview and put it in my view's coordinate space
_originalFrame = [[aView superview] convertRect: aView.frame toView: self];
Every view in iOS have a coordinate system. A coordinate system is just like a graph, which has x axis(horizontal line) and y axis(vertical line). The point at which the lines interesect is called origin. A point is represented by (x, y). For example, (2, 1) means that the point is 2 pixels left, and 1 pixel down.
You can read up more about coordinate systems here - http://en.wikipedia.org/wiki/Coordinate_system
But what you need to know is that, in iOS, every view has it's OWN coordinate system, where the top left corner is the origin. X axis goes on increasing to the right, and y axis goes on increasing down.
For the converting points question, take this example.
There is a view, called V1, which is 100 pixels wide and 100 pixels high. Now inside that, there is another view, called V2, at (10, 10, 50, 50) which means that (10, 10) is the point in V1's coordinate system where the top left corner of V2 should be located, and (50, 50) is the width and height of V2. Now, take a point INSIDE V2's coordinate system, say (20, 20). Now, what would that point be inside V1's coordinate system? That is what the methods are for(of course you can calculate themselves, but they save you extra work). For the record, the point in V1 would be (30, 30).
Hope this helps.
Thank you all for posting the question and your answers: It helped me get this sorted out.
My view controller has it's normal view.
Inside that view there are a number of grouping views that do little more than give their child views a clean interaction with auto layout constraints.
Inside one of those grouping views I have an Add button that presents a popover view controller where the user enters some information.
view
--groupingView
----addButton
During device rotation the view controller is alerted via the UIPopoverViewControllerDelegate call popoverController:willRepositionPopoverToRect:inView:
- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view
{
*rect = [self.addButton convertRect:self.addbutton.bounds toView:*view];
}
The essential part that comes from the explanation given by the first two answers above was that the rect I needed to convert from was the bounds of the add button, not its frame.
I haven't tried this with a more complex view hierarchy, but I suspect that by using the view supplied in the method call (inView:) we get around the complications of multi-tiered leaf view kinds of ugliness.
I used this post to apply in my case. Hope this will help another reader in the future.
A view can only see its immediate children and parent views. It can't see its grand parents or its grandchildren views.
So, in my case, I have a grand parent view called self.view, in this self.view I have added subviews called self.child1OfView, self.child2OfView. In self.child1OfView, I have added subviews called self.child1OfView1, self.child2OfView1.
Now if I physically move self.child1OfView1 to an area outside the boundary of self.child1OfView to anther spot on self.view, then to calculator the new position for the self.child1OfView1 within the self.view:
CGPoint newPoint = [self.view convertPoint:self.child1OfView1.center fromView:self.child1OfView];
You can see below code so you can understand that how it actually works.
let scrollViewTemp = UIScrollView.init(frame: CGRect.init(x: 10, y: 10, width: deviceWidth - 20, height: deviceHeight - 20))
override func viewDidLoad() {
super.viewDidLoad()
scrollViewTemp.backgroundColor = UIColor.lightGray
scrollViewTemp.contentSize = CGSize.init(width: 2000, height: 2000)
self.view.addSubview(scrollViewTemp)
let viewTemp = UIView.init(frame: CGRect.init(x: 100, y: 100, width: 150, height: 150))
viewTemp.backgroundColor = UIColor.green
self.view.addSubview(viewTemp)
let viewSecond = UIView.init(frame: CGRect.init(x: 100, y: 700, width: 300, height: 300))
viewSecond.backgroundColor = UIColor.red
self.view.addSubview(viewSecond)
self.view.convert(viewTemp.frame, from: scrollViewTemp)
print(viewTemp.frame)
/* First take one point CGPoint(x: 10, y: 10) of viewTemp frame,then give distance from viewSecond frame to this point.
*/
let point = viewSecond.convert(CGPoint(x: 10, y: 10), from: viewTemp)
//output: (10.0, -190.0)
print(point)
/* First take one point CGPoint(x: 10, y: 10) of viewSecond frame,then give distance from viewTemp frame to this point.
*/
let point1 = viewSecond.convert(CGPoint(x: 10, y: 10), to: viewTemp)
//output: (10.0, 210.0)
print(point1)
/* First take one rect CGRect(x: 10, y: 10, width: 20, height: 20) of viewSecond frame,then give distance from viewTemp frame to this rect.
*/
let rect1 = viewSecond.convert(CGRect(x: 10, y: 10, width: 20, height: 20), to: viewTemp)
//output: (10.0, 210.0, 20.0, 20.0)
print(rect1)
/* First take one rect CGRect(x: 10, y: 10, width: 20, height: 20) of viewTemp frame,then give distance from viewSecond frame to this rect.
*/
let rect = viewSecond.convert(CGRect(x: 10, y: 10, width: 20, height: 20), from: viewTemp)
//output: (10.0, -190.0, 20.0, 20.0)
print(rect)
}
I read the answer and understand the mechanics but I think the final example is not correct. According to the API doc, the center property of a view contains the known center point of the view in the superview’s coordinate system.
If this is the case, than I think it would not make sense to try to ask the superview to convert the center of a subview FROM the subview coordinate system because the value is not in the subview coordinate system. What would make sense is to do the opposite i.e. convert from the superview coordinate system to that of a subview...
You can do it in two ways (both should yield the same value):
CGPoint centerInSubview = [subview convertPoint:subview.center fromView:subview.superview];
or
CGPoint centerInSubview = [subview.superview convertPoint:subview.center toView:subview];
Am I way off in understanding how this should work?
One more important point about using these APIs. Be sure that the parent view chain is complete between the rect you are converting and the to/from view.
For example - aView, bView, and cView -
aView is a subview of bView
we want to convert aView.frame to cView
If we try to execute the method before bView has been added as a subview of cView, we will get back a bunk response. Unfortunately there is no protection built into the methods for this case. This may seem obvious, but it is something to be aware of in cases where the conversion goes through a long chain of parents.

Resources