Scaling UITextView using contentScaleFactor property - objective-c

I am trying to create a higher resolution image of a UIView, specifically UITextView.
This question and answer is exactly what I am trying to figure out:
Retain the resolution of the label after scaling in iphone
But, when I do the same, my text is still blurry:
self.view.transform = CGAffineTransformMakeScale(2.f, 2.f);
[myText setContentScaleFactor:2.f]; // myText is a subview of self.view object
I have also tried the same in the Apple sample project "UICatalog" to UILabel and it is also blurry.
I can't understand why it would work for Warrior from the other question and not for me. I would have asked there — but I can't seem to leave a comment or a question there.

Setting the contentScaleFactor and contentsScale is in fact the key, as #dbotha pointed out, however you have to walk the view and layer hierarchies separately in order to reach every internal CATiledLayer that actually does the text rendering. Adding the screen scale might also make sense.
So the correct implementation would be something like this:
- (void)updateForZoomScale:(CGFloat)zoomScale {
CGFloat screenAndZoomScale = zoomScale * [UIScreen mainScreen].scale;
// Walk the layer and view hierarchies separately. We need to reach all tiled layers.
[self applyScale:(zoomScale * [UIScreen mainScreen].scale) toView:self.textView];
[self applyScale:(zoomScale * [UIScreen mainScreen].scale) toLayer:self.textView.layer];
}
- (void)applyScale:(CGFloat)scale toView:(UIView *)view {
view.contentScaleFactor = scale;
for (UIView *subview in view.subviews) {
[self applyScale:scale toView:subview];
}
}
- (void)applyScale:(CGFloat)scale toLayer:(CALayer *)layer {
layer.contentsScale = scale;
for (CALayer *sublayer in layer.sublayers) {
[self applyScale:scale toLayer:sublayer];
}
}

UITextView has textInputView property, which "both draws the text and provides a coordinate system" (https://developer.apple.com/reference/uikit/uitextinput/1614564-textinputview)
So i'm using the following code to scale UITextView - without any font changes, without using any "CALayer" property and keeping high quality:
float screenScaleFactor = [[UIScreen mainScreen] scale];
float scale = 5.0;
textView.transform = CGAffineTransformMakeScale(scale, scale);
textView.textInputView.contentScaleFactor = screenScaleFactor * scale;
Comment the last line if you need low quality (but better performance) scaling.
Transform uses view.center as scaling center point, so adding a 'translate transform' is needed to scale around view corner.

Be sure to apply the contentScaleFactor to all subviews of the UITextView. I've just tested the following with a UITextView and found it to work:
- (void)applyScale:(CGFloat)scale toView:(UIView *)view {
view.contentScaleFactor = scale;
view.layer.contentsScale = scale;
for (UIView *subview in view.subviews) {
[self applyScale:scale toView:subview];
}
}

Related

iOS 8.0.2 : UIView frame animation not working when UINavigationController contained inside RootViewController

I've created a RootViewController / RootView that:
Handles the content layout for the app
Exposes and interface for performing application level behaviors, like presenting the "hamburger" menu or overlay views with CAKeyframe animations.
This is in accordance with good practice.
The Problem:
When the main content view presents a form, there's a utility to animate the frame of that view, when a field is selected that would otherwise be obscured by the keyboard. This has been working fine all the way up until iOS 8.0.2
On iOS 8.0.2 the frame for the form will no longer animate if you set a negative value for origin.y. Instead of going from the current origin.y to the required origin.y it jerks down by the amount it was supposed to move, then animates back to 0.
If I present the form outside of the RootVC it works correctly.
What I've tried:
Checked that RootView is not doing anything in layout subviews to prevent the animation. (In iOS8.0 it was. I removed this and problem was solved. Only to return in iOS8.0.2)
Checked the BeginFromCurrentState flags.
Instead of animating the form view, animate [UIScreen mainScreen].keyWindow. Works but causes some other side effects that I don't want.
Question:
What has changed with animation of UIViewControllers that are contained in another view in iOS8.0.2. It seems to be something very fundamental.
The code that animates frame to move input fields out the keyboard's way:
Looks something like this:
- (void)scrollToAccommodateField:(UIView *)view
{
UIView *rootView = [UIApplication sharedApplication].keyWindow.rootViewController.view;
CGPoint position = [view convertPoint:view.bounds.origin toView:rootView];
CGFloat y = position.y;
CGFloat scrollAmount = 0;
CGFloat margin = 25;
CGSize screenSize = [self screenSizeWithOrientation:[UIApplication sharedApplication].statusBarOrientation];
CGSize accessorySize = CGSizeMake(_view.width, 44);
CGFloat maxVisibleY = screenSize.height - [self keyboardSize].height - accessorySize.height - margin;
if (y > maxVisibleY)
{
scrollAmount = maxVisibleY - y;
CGFloat scrollDelta = scrollAmount - _currentScrollAmount;
_currentScrollAmount = scrollAmount;
[self scrollByAmount:scrollDelta];
}
else
{
if (_currentScrollAmount != 0)
{
_currentScrollAmount = 0;
[UIView transitionWithView:_view duration:0.30
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseOut animations:^
{
_view.frame = [_view bounds];
} completion:nil];
}
}
}
Update:
I've since installed TPKeyboardAvoiding pod and its working very well. . leaving this open, in case its of interest to others.

How do I size a UITextView to its content on iOS 7?

I've been using the accepted answer here for years.
On iOS 7, the contentSize.height becomes the frame.height-8, regardless of text content.
What's a working method to adjust the height on iOS 7?
I favor this minimal code change: Just add these two lines after addSubview and before grabbing the height of the frame
...
[scrollView1 addSubview: myTextView];
[myTextView sizeToFit]; //added
[myTextView layoutIfNeeded]; //added
CGRect frame = myTextView.frame;
...
This is tested backwards compatible with iOS 6. NOTE that it shrink-wraps the width. If you're just interested in the height and have a fixed width, just grab the new height but set the original width, and it works just as before on both iOS 6 and 7.
(Speculation: it does size to fit on iOS 7 as well, but the layout is updated later or in a separate thread, and that this forces the layout immediately so that its frame is updated in time for using its height value a few lines later in the same thread.)
NOTES:
1) You might or might not have implemented the outer container resize this way. It does seem to be a common snippet, though, and I've used it in my projects.
2) Since sizeToFit seems to work as expected on iOS 7, you likely don't need the premature addSubView. Whether it will still work on iOS 6 then is untested by me.
3) Speculation: The extra layoutIfNeeded mid-thread might be costly. The alternative as I see it is to resize the outer container on the layout callback (fired or not depending on if the OS decides whether layout is needed or not) where the outer container resize will cause another layout update. Both updates might be combined with other layout updates to be more efficient. If you do have such a solution and you can show that it is more efficient, add it as answer and I'll be sure to mention it here.
Since I'm using Auto Layout, I use the value of [textView sizeThatFits:CGSizeMake(textView.frame.size.width, CGFLOAT_MAX)].height to update the constant of the textView's height UILayoutConstraint.
I use an adapted version of madmik's answer that eliminates the fudge factor:
- (CGFloat)measureHeightOfUITextView:(UITextView *)textView
{
if ([textView respondsToSelector:#selector(snapshotViewAfterScreenUpdates:)])
{
// This is the code for iOS 7. contentSize no longer returns the correct value, so
// we have to calculate it.
//
// This is partly borrowed from HPGrowingTextView, but I've replaced the
// magic fudge factors with the calculated values (having worked out where
// they came from)
CGRect frame = textView.bounds;
// Take account of the padding added around the text.
UIEdgeInsets textContainerInsets = textView.textContainerInset;
UIEdgeInsets contentInsets = textView.contentInset;
CGFloat leftRightPadding = textContainerInsets.left + textContainerInsets.right + textView.textContainer.lineFragmentPadding * 2 + contentInsets.left + contentInsets.right;
CGFloat topBottomPadding = textContainerInsets.top + textContainerInsets.bottom + contentInsets.top + contentInsets.bottom;
frame.size.width -= leftRightPadding;
frame.size.height -= topBottomPadding;
NSString *textToMeasure = textView.text;
if ([textToMeasure hasSuffix:#"\n"])
{
textToMeasure = [NSString stringWithFormat:#"%#-", textView.text];
}
// NSString class method: boundingRectWithSize:options:attributes:context is
// available only on ios7.0 sdk.
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
NSDictionary *attributes = #{ NSFontAttributeName: textView.font, NSParagraphStyleAttributeName : paragraphStyle };
CGRect size = [textToMeasure boundingRectWithSize:CGSizeMake(CGRectGetWidth(frame), MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attributes
context:nil];
CGFloat measuredHeight = ceilf(CGRectGetHeight(size) + topBottomPadding);
return measuredHeight;
}
else
{
return textView.contentSize.height;
}
}
Based on other answers, I made it work(in Swift).
This solves the problem with newline character.
textView.sizeToFit()
textView.layoutIfNeeded()
let height = textView.sizeThatFits(CGSizeMake(textView.frame.size.width, CGFloat.max)).height
textView.contentSize.height = height
Auto Layout is needed.
If you're using Auto Layout, you could create a trivial UITextView subclass that self-sizes the text view height to fit the content:
#interface ContentHeightTextView : UITextView
#end
#interface ContentHeightTextView ()
#property (nonatomic, strong) NSLayoutConstraint *heightConstraint;
#end
#implementation ContentHeightTextView
- (void)layoutSubviews
{
[super layoutSubviews];
CGSize size = [self sizeThatFits:CGSizeMake(self.bounds.size.width, FLT_MAX)];
if (!self.heightConstraint) {
self.heightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0f constant:size.height];
[self addConstraint:self.heightConstraint];
}
self.heightConstraint.constant = size.height;
[super layoutSubviews];
}
#end
Of course, the text view's width and position must be defined by additional constraints configured elsewhere in the program.
If you create this custom text view in IB, give the text view a height constraint in order to satisfy Xcode; just make sure the height constraint created in IB is merely a placeholder (i.e., tick the box that says "Remove at build time").
An alternative way to implement the UITextView subclass is as follows (this implementation might qualify as best practice):
#interface ContentHeightTextView ()
#property (nonatomic, strong) NSLayoutConstraint *heightConstraint;
#end
#implementation ContentHeightTextView
- (void)layoutSubviews
{
[super layoutSubviews];
[self setNeedsUpdateConstraints];
}
- (void)updateConstraints
{
CGSize size = [self sizeThatFits:CGSizeMake(self.bounds.size.width, FLT_MAX)];
if (!self.heightConstraint) {
self.heightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0f constant:size.height];
[self addConstraint:self.heightConstraint];
}
self.heightConstraint.constant = size.height;
[super updateConstraints];
}
#end
If you are using auto-layout, you can use the following UITextView subclass that adds an intrinsic height:
#implementation SelfSizingTextView
- (void)setText:(NSString *)text
{
[super setText:text];
[self invalidateIntrinsicContentSize];
}
- (void)setFont:(UIFont *)font
{
[super setFont:font];
[self invalidateIntrinsicContentSize];
}
- (CGSize)intrinsicContentSize
{
CGFloat width = self.frame.size.width;
CGSize size = [self sizeThatFits:CGSizeMake(width, MAXFLOAT)];
return CGSizeMake(UIViewNoIntrinsicMetric, size.height);
}
#end
this method seems to work.
// Code from apple developer forum - #Steve Krulewitz, #Mark Marszal, #Eric Silverberg
- (CGFloat)measureHeight
{
if ([self respondsToSelector:#selector(snapshotViewAfterScreenUpdates:)])
{
CGRect frame = internalTextView.bounds;
CGSize fudgeFactor;
// The padding added around the text on iOS6 and iOS7 is different.
fudgeFactor = CGSizeMake(10.0, 16.0);
frame.size.height -= fudgeFactor.height;
frame.size.width -= fudgeFactor.width;
NSMutableAttributedString* textToMeasure;
if(internalTextView.attributedText && internalTextView.attributedText.length > 0){
textToMeasure = [[NSMutableAttributedString alloc] initWithAttributedString:internalTextView.attributedText];
}
else{
textToMeasure = [[NSMutableAttributedString alloc] initWithString:internalTextView.text];
[textToMeasure addAttribute:NSFontAttributeName value:internalTextView.font range:NSMakeRange(0, textToMeasure.length)];
}
if ([textToMeasure.string hasSuffix:#"\n"])
{
[textToMeasure appendAttributedString:[[NSAttributedString alloc] initWithString:#"-" attributes:#{NSFontAttributeName: internalTextView.font}]];
}
// NSAttributedString class method: boundingRectWithSize:options:context is
// available only on ios7.0 sdk.
CGRect size = [textToMeasure boundingRectWithSize:CGSizeMake(CGRectGetWidth(frame), MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
return CGRectGetHeight(size) + fudgeFactor.height;
}
else
{
return self.internalTextView.contentSize.height;
}
}
If you're using iOS 7+, you can just turn on auto layout, pin each of the sides of the text view to the edge of its parent view, and it works fine. No additional code needed.
Not sure if this was always the case but the following is true since at least iOS 10.
UITextView implements the intrinsicContentSize property if scrollEnabled == NO. That means you just need to make sure the width of the text view is constrained enough and then you can use the intrinsic content height (either via Auto Layout content hugging/compression resistance priorities or directly using the value during manual layout).
Unfortunately, this behavior is not documented. Apple could have easily saved us all some headaches… no need for an extra height constraint, subclassing, etc.
In iOS 8 you'll in inherit some content offset from the parent, which you need to get rid of as well.
A subclass example
// Originally from https://github.com/Nikita2k/resizableTextView
#import "ResizableTextView.h"
#implementation ResizableTextView
- (void) updateConstraints {
// calculate contentSize manually
// ios7 doesn't calculate it before viewDidAppear and we'll get here before
CGSize contentSize = [self sizeThatFits:CGSizeMake(self.frame.size.width, FLT_MAX)];
// set the height constraint to change textView height
[self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
if (constraint.firstAttribute == NSLayoutAttributeHeight) {
constraint.constant = contentSize.height;
*stop = YES;
}
}];
[super updateConstraints];
}
- (void)setContentOffset:(CGPoint)contentOffset
{
// In iOS 8 we seem to be inheriting the content offset from the parent.
// I'm not interested
}
#end
In storyboard, if using constraints, make sure you are constrained to your superview in the 'ruler' tab of the right-hand pane on xcode for the UITextView. My problem was that I had a constraint of -80 pts on the 'Trailing space to'.
Guys using autolayout and your sizetofit isn't working, then please check your width constraint once. If you had missed the width constraint then the height will be accurate.
No need to use any other API. just one line would fix all the issue.
[_textView sizeToFit];
Here, I was only concerned with height, keeping the width fixed and had missed the width constraint of my TextView in storyboard.
And this was to show up the dynamic content from the services.
Hope this might help..
I wrote a category over UITextView:
- (CGSize)intrinsicContentSize {
return self.contentSize;
}
- (void)setContentSize:(CGSize)contentSize {
[super setContentSize:contentSize];
[self invalidateIntrinsicContentSize];
}
When UIKit sets its contentSize, UITextView adjusts its intrinsic content size. That plays nicely with autolayout.
The answer given by bilobatum worked perfectly With auto layout, i.e subclassing the textview.
If you want to limit the height of the text view add another constraint (I added it using storyboard i.e. height <= 166 (height as per your need))
Then inside subclass reduce the priority of height constraint to 750 (self.heightConstraint.priority = 750) to avoid conflict between height constraint added in subclass and height constraint added on storyboard.

Animating a UIView frame, subview UIScrollView doesn't always animate

In this example.
I'm animating the PhotoViewerViewController's frame when I animate out the tabBarController (for fullscreen effect). The PhotoViewer uses a uiscrollview to generate the same sort of effect Apple's photos app does. For whatever reason sometimes it animates along with my PhotoViewer frame, and sometimes it doesn't.
You can see in the first example it jumps when increasing the frame size, but animates nicely when decreasing the frame size (and restoring the tab bar).
However in this example when the photo is vertical it jumps in both directions.
In both cases if I zoom in on the photo at all with the scrollview, it animates correctly in both directions.
I know the evidence is there, but I can't put my finger on what's happening here.
Here's my animation block:
void (^updateProperties) (void) = ^ {
self.navigationController.navigationBar.alpha = hidden ? 0.0f : 1.0f;
self.navigationController.toolbar.alpha = hidden ? 0.0f : 1.0f;
if (self.tabBarController) {
int height = self.tabBarController.tabBar.bounds.size.height;
for(UIView *view in self.tabBarController.view.subviews)
{
int newHeight;
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if (UIInterfaceOrientationIsPortrait(orientation)) {
newHeight = hidden ? [[UIScreen mainScreen] bounds].size.height : [[UIScreen mainScreen] bounds].size.height - height;
} else {
newHeight = hidden ? [[UIScreen mainScreen] bounds].size.width : [[UIScreen mainScreen] bounds].size.width - height;
}
if([view isKindOfClass:[UITabBar class]])
{
[view setFrame:CGRectMake(view.frame.origin.x, newHeight, view.frame.size.width, view.frame.size.height)];
}
else
{
CGRect newFrame = CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, newHeight);
[view setFrame:newFrame];
// update our VC frame with animation
[self.view setFrame:newFrame];
}
}
}
};
Code adapted from a post on SO: UITabBar wont hide
Full source on github.
In general, if animations sometimes work and sometimes don't, it's because in the latter case a delayed perform is causing them to be effectively cancelled by setting the property concerned to its final value.
In the particular case of UIScrollView it often happens that -layoutSubviews is called without animation one run loop after your animation blocks have closed. For this reason I usually try to avoid doing any layout in that method where I have a UIScrollView in the view hierarchy (because particularly prior to iOS 5, UIScrollView is really trigger-happy on setting itself to need layout).
I would recommend removing your call to -[EGOPhotoImageView layoutScrollViewAnimated:] from -layoutSubviews and add it to an overridden -setFrame: instead.
If you can target iOS4 and above, take a look at UIViewAnimationOptionLayoutSubviews too. Using a block-based animation with this as an option might just work without changing anything else.
I tried your github code on iPad 4.2 and 4.3 simulators and it works fine... no image resizing whatsoever! There might be some version issues.
Also, I tried changing your animations block and the following serves the same purpose:
void (^updateProperties) (void) = ^ {
self.navigationController.navigationBar.alpha = hidden ? 0.0f : 1.0f;
self.navigationController.toolbar.alpha = hidden ? 0.0f : 1.0f; }
Let me know if I'm missing something :)

How to rotate a NSTextView

I want to have rotatable text edit area like in Adobe Illustrator. I tried several things including subclassing NSTextView and adding coordinate system rotation before the drawing code like this:
#implementation MyNSTextView
-(void)drawRect:(NSRect)dirtyRect
{
NSAffineTransform * trafo = [NSAffineTransform transform];
[trafo rotateByDegrees:45];
[trafo concat];
[super drawRect:dirtyRect];
}
-(void)drawInsertionPointInRect:(NSRect)rect color:(NSColor *)color turnedOn:(BOOL)flag
{
NSAffineTransform * trafo = [NSAffineTransform transform];
[trafo rotateByDegrees:45];
[trafo concat];
[super drawInsertionPointInRect:rect color:color turnedOn:flag];
}
Which results in corrupted display of the text. I also tried to implement the functionality myself by subclassing NSView and assembling the text architecture programmatically. I draw the string with the drawGlyphsForRange: method of my NSLayoutManager. This partly works but gets complicated because I have to rotate the coordinates for the drawing and for handling the mouse clicks I have to convert the mouse location back to old coordinates.
So is this the best way to do it or is there something else?
Edit: I now tried to use CALayer like this:
#implementation MyNSTextView
-(id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
if (self) {
CALayer *myLayer = [CALayer layer];
myLayer.transform = CATransform3DMakeRotation(1.6,1,0,1.0);
[self setLayer:myLayer];
[self setWantsLayer:YES];
}
return self;
}
But this yields a TextView which doesn't show any text (only a cursor) and is not rotated. Is there something else to add to make it work??
You are making it too complicated. Simply set the rotation on the view itself:
[myView setFrameRotation:angleInDegrees];
There is also setFrameCenterRotation, but that is supposed to work only for layer-backed views (which is OK).
There is one caveat which I am still investigating myself: The blinking text cursor becomes really fat when the view is rotated.
Why not make the views layer-backed? You can then just apply an affine transform to the view's layer.
yourView.layer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1.0);
The problem is that you're creating an NSAffineTransform, but you're not attaching it to your text view.

UIScrollView setZoomScale not working?

I am struggeling with my UIScrollview to get it to zoom-in the underlying UIImageView. In my view controller I set
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return myImageView;
}
In the viewDidLoad method I try to set the zoomScale to 2 as follows (note the UIImageView and Image is set in Interface Builder):
- (void)viewDidLoad {
[super viewDidLoad];
myScrollView.contentSize = CGSizeMake(myImageView.frame.size.width, myImageView.frame.size.height);
myScrollView.contentOffset = CGPointMake(941.0, 990.0);
myScrollView.minimumZoomScale = 0.1;
myScrollView.maximumZoomScale = 10.0;
myScrollView.zoomScale = 0.7;
myScrollView.clipsToBounds = YES;
myScrollView.delegate = self;
NSLog(#"zoomScale: %.1f, minZoolScale: %.3f", myScrollView.zoomScale, myScrollView.minimumZoomScale);
}
I tried a few variations of this, but the NSLog always shows a zoomScale of 1.0.
Any ideas where I screw this one up?
I finally got this to work. what caused the problem was the delegate call being at the end. I now moved it up and .... here we go.
New code looks like this:
- (void)viewDidLoad {
[super viewDidLoad];
myScrollView.delegate = self;
myScrollView.contentSize = CGSizeMake(myImageView.frame.size.width, myImageView.frame.size.height);
myScrollView.contentOffset = CGPointMake(941.0, 990.0);
myScrollView.minimumZoomScale = 0.1;
myScrollView.maximumZoomScale = 10.0;
myScrollView.zoomScale = 0.7;
myScrollView.clipsToBounds = YES;
}
Here is another example I made. This one is using an image that is included in the resource folder. Compared to the one you have this one adds the UIImageView to the view as a subview and then changes the zoom to the whole view.
-(void)viewDidLoad{
[super viewDidLoad];
UIImage *image = [UIImage imageNamed:#"random.jpg"];
imageView = [[UIImageView alloc] initWithImage:image];
[self.view addSubview:imageView];
[(UIScrollView *) self.view setContentSize:[image size]];
[(UIScrollView *) self.view setMaximumZoomScale:2.0];
[(UIScrollView *) self.view setMinimumZoomScale:0.5];
}
I know this is quite late as answers go, but the problem is that your code calls zoomScale before it sets the delegate. You are right the other things in there don't require the delegate, but zoomScale does because it has to be able to call back when the zoom is complete. At least that's how I think it works.
My code must be completely crazy because the scale that I use is completely opposite to what tutorials and others are doing. For me, minScale = 1 which indicates that the image is fully zoomed out and fits the UIImageView that contains it.
Here's my code:
[self.imageView setImage:image];
// Makes the content size the same size as the imageView size.
// Since the image size and the scroll view size should be the same, the scroll view shouldn't scroll, only bounce.
self.scrollView.contentSize = self.imageView.frame.size;
// despite what tutorials say, the scale actually goes from one (image sized to fit screen) to max (image at actual resolution)
CGRect scrollViewFrame = self.scrollView.frame;
CGFloat minScale = 1;
// max is calculated by finding the max ratio factor of the image size to the scroll view size (which will change based on the device)
CGFloat scaleWidth = image.size.width / scrollViewFrame.size.width;
CGFloat scaleHeight = image.size.height / scrollViewFrame.size.height;
self.scrollView.maximumZoomScale = MAX(scaleWidth, scaleHeight);
self.scrollView.minimumZoomScale = minScale;
// ensure we are zoomed out fully
self.scrollView.zoomScale = minScale;
This works as I expect. When I load the image into the UIImageView, it is fully zoomed out. I can then zoom in and then I can pan the image.