How to center UIImageView in ScrollView (Zooming) - objective-c

I'm trying to zooming UIImageView in UIScrollView. And i've got this:
-(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return imageView;
}
-(void)scrollViewDidZoom:(UIScrollView *)scrollView {
[imageView setCenter:CGPointMake(scrollView.center.x, scrollView.center.y)];
}
-(void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
}
Problem is: When i'm zooming out image it's centered, but when i'm zooming in, image is shifted to left up corner.
Where is problem with my code?

Answer is remove all from scrollViewDidZoom: and add:
[self centerScrollViewContent];
Method implementation:
- (void)centerScrollViewContents {
CGSize boundsSize = self.scrollView.bounds.size;
CGRect contentsFrame = self.containerView.frame;
if (contentsFrame.size.width < boundsSize.width) {
contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0f;
} else {
contentsFrame.origin.x = 0.0f;
}
if (contentsFrame.size.height < boundsSize.height) {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0f;
} else {
contentsFrame.origin.y = 0.0f;
}
self.containerView.frame = contentsFrame;
}

Related

Keep UIView fixed when attached to UIWindow in iOS8

Up until iOS7, if I wanted to create a sort of a fixed "background" that would not rotate, animate or scale whatsoever, I would have simply attached a UIView to the main UIWindow.
It was a kind of hack... but it served very well its purpose.
In iOS8 they changed the way UIWindow behave among other things (for example now [UIScreen mainScreen].bounds returns different size depending on the orientation).
Anyway...
My question is: how do I get iOS8 to behave as iOS7 and below?
I tried already to create two ViewControllers, one with shouldAutorotate method set to NO but with no luck.
The desired effect is the one shown here:
Any ideas?
If you build your app using iOS8 SDK, you can use nativeBounds, which will always return bounds for portrait orientation, but in the pixels, not in the points.
To get right bounds in points you should divide them by nativeScale.
if ([[UIScreen mainScreen] respondsToSelector:#selector(nativeBounds)]) {
CGFloat scaleFactor = 1.0f / [self nativeScale];
CGRect realBounds = CGRectApplyAffineTransform([self nativeBounds],
CGAffineTransformMakeScale(scaleFactor, scaleFactor));
}
So I think you can subscribe to didChangeStatusBarOrientation notification and in your method calculate new frame for green square and justify rotation:
rect.transform = CGAffineTransformMakeRotation((CGFloat)M_PI * (90.f) / 180.0f);
for landscape right orientation
try to use this subclass with fixed rotation:
#interface RotationView : UIView {
struct {
CGPoint portOrigin;
CGPoint leftOrigin;
CGPoint rightOrigin;
CGPoint upsideOrigin;
} origins;
struct {
NSInteger portDegree;
NSInteger leftDegree;
NSInteger rightDegree;
NSInteger upsideDegree;
} degrees;
}
#end
#implementation RotationView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(deviceOrientationDidChange:)
name:UIDeviceOrientationDidChangeNotification object:nil];
}
return self;
}
- (void)didMoveToSuperview {
[super didMoveToSuperview];
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if (orientation == UIInterfaceOrientationPortrait) {
origins.portOrigin = self.frame.origin;
origins.rightOrigin = CGPointMake(CGRectGetMinY(self.frame), CGRectGetWidth(self.superview.frame) - CGRectGetMinX(self.frame) - CGRectGetWidth(self.frame));
origins.leftOrigin = CGPointMake(CGRectGetHeight(self.superview.frame) - CGRectGetMinY(self.frame) - CGRectGetHeight(self.frame), CGRectGetMinX(self.frame));
origins.upsideOrigin = CGPointMake(CGRectGetWidth(self.superview.frame) - CGRectGetMinX(self.frame) - CGRectGetWidth(self.frame),
CGRectGetHeight(self.superview.frame) - CGRectGetMinY(self.frame) - CGRectGetHeight(self.frame));
degrees.portDegree = 0.0F;
degrees.rightDegree = 270;
degrees.leftDegree = 90.0F;
degrees.upsideDegree = 180.0F;
} else if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
origins.portOrigin = CGPointMake(CGRectGetWidth(self.superview.frame) - CGRectGetMinX(self.frame) - CGRectGetWidth(self.frame),
CGRectGetHeight(self.superview.frame) - CGRectGetMinY(self.frame) - CGRectGetHeight(self.frame));
origins.leftOrigin = CGPointMake(CGRectGetMinY(self.frame), CGRectGetWidth(self.superview.frame) - CGRectGetMinX(self.frame) - CGRectGetWidth(self.frame));
origins.rightOrigin = CGPointMake(CGRectGetHeight(self.superview.frame) - CGRectGetMinY(self.frame) - CGRectGetHeight(self.frame), CGRectGetMinX(self.frame));
origins.upsideOrigin = self.frame.origin;
degrees.portDegree = 180.0F;
degrees.rightDegree = 90.0F;
degrees.leftDegree = 270;
degrees.upsideDegree = 0.0F;
} else if (orientation == UIInterfaceOrientationLandscapeLeft) {
origins.upsideOrigin = CGPointMake(CGRectGetHeight(self.superview.frame) - CGRectGetMinY(self.frame) - CGRectGetHeight(self.frame), CGRectGetMinX(self.frame));
origins.leftOrigin = self.frame.origin;
origins.rightOrigin = CGPointMake(CGRectGetWidth(self.superview.frame) - CGRectGetMinX(self.frame) - CGRectGetWidth(self.frame),
CGRectGetHeight(self.superview.frame) - CGRectGetMinY(self.frame) - CGRectGetHeight(self.frame));
origins.portOrigin = CGPointMake(CGRectGetMinY(self.frame), CGRectGetWidth(self.superview.frame) - CGRectGetMinX(self.frame) - CGRectGetWidth(self.frame));
degrees.upsideDegree = 90.0F;
degrees.leftDegree = 0.0F;
degrees.rightDegree = 180.0F;
degrees.portDegree = 270.0;
} else if (orientation == UIInterfaceOrientationLandscapeRight) {
origins.portOrigin = CGPointMake(CGRectGetHeight(self.superview.frame) - CGRectGetMinY(self.frame) - CGRectGetHeight(self.frame), CGRectGetMinX(self.frame));
origins.leftOrigin = CGPointMake(CGRectGetWidth(self.superview.frame) - CGRectGetMinX(self.frame) - CGRectGetWidth(self.frame),
CGRectGetHeight(self.superview.frame) - CGRectGetMinY(self.frame) - CGRectGetHeight(self.frame));
origins.rightOrigin = self.frame.origin;
origins.upsideOrigin = CGPointMake(CGRectGetMinY(self.frame), CGRectGetWidth(self.superview.frame) - CGRectGetMinX(self.frame) - CGRectGetWidth(self.frame));
degrees.portDegree = 90.0F;
degrees.leftDegree = 180.0F;
degrees.rightDegree = 0.0F;
degrees.upsideDegree = 270.0F;
}
}
#pragma mark Manual oritentation change
#define RADIANS(degrees) ((degrees * (float)M_PI) / 180.0f)
- (void)deviceOrientationDidChange:(NSNotification *)notification {
if (!self.superview) {
return;
}
if ([self.superview isKindOfClass:[UIWindow class]]) {
[self setTransformForCurrentOrientation];
}
}
- (void)setTransformForCurrentOrientation {
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if (UIInterfaceOrientationIsLandscape(orientation)) {
if (orientation == UIInterfaceOrientationLandscapeLeft) {
[self setTransform:CGAffineTransformMakeRotation(RADIANS(degrees.leftDegree))];
[self setOrigin:origins.leftOrigin];
} else {
[self setTransform:CGAffineTransformMakeRotation(RADIANS(degrees.rightDegree))];
[self setOrigin:origins.rightOrigin];
}
} else {
if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
[self setTransform:CGAffineTransformMakeRotation(RADIANS(degrees.upsideDegree))];
[self setOrigin:origins.upsideOrigin];
} else {
[self setTransform:CGAffineTransformMakeRotation(RADIANS(degrees.portDegree))];
[self setOrigin:origins.portOrigin];
}
}
}
#end
setOrigin is category method:
- (void)setX:(CGFloat)x {
CGRect frame = self.frame;
frame.origin.x = x;
self.frame = frame;
}
- (void)setY:(CGFloat)y {
CGRect frame = self.frame;
frame.origin.y = y;
self.frame = frame;
}
- (void)setOrigin:(CGPoint)origin {
[self setX:origin.x];
[self setY:origin.y];
}

Tableview scroll to top not going all the way

I have a navigation bar that I've manually coded to animate the frame of depending on the offset of the scrollView (tableView). Below is a screenshot of what it looks like unscrolled.
Now after setScrollOffset:(0,0) is invoked (by scrollsToTop, not me manually - e.g. by tapping status bar), the scrollview scrolls to the top, but at the position at which there used to be no navigation bar. I can manually scroll the last 44px or so after the animation happens, but obviously thats not the behavior that's expected.
Here is my code for hiding the navbar:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGRect frame = self.navigationController.navigationBar.frame;
CGFloat size = frame.size.height - kMacro1;
CGFloat framePercentageHidden = ((kMacro2 - frame.origin.y) / (frame.size.height - 1));
CGFloat scrollOffset = scrollView.contentOffset.y;
CGFloat scrollDiff = scrollOffset - self.previousScrollViewYOffset;
CGFloat scrollHeight = scrollView.frame.size.height;
CGFloat scrollContentSizeHeight = scrollView.contentSize.height + scrollView.contentInset.bottom;
if (scrollOffset <= -scrollView.contentInset.top) {
frame.origin.y = kMacro2;
} else if ((scrollOffset + scrollHeight) >= scrollContentSizeHeight) {
frame.origin.y = -size;
} else {
frame.origin.y = MIN(kMacro2, MAX(-size, frame.origin.y - scrollDiff));
}
[self.navigationController.navigationBar setFrame:frame];
[self updateBarButtonItems:(1 - framePercentageHidden)];
self.previousScrollViewYOffset = scrollOffset;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self stoppedScrolling];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
willDecelerate:(BOOL)decelerate
{
if (!decelerate) {
[self stoppedScrolling];
}
}
- (void)stoppedScrolling
{
CGRect frame = self.navigationController.navigationBar.frame;
if (frame.origin.y < kMacro2) {
[self animateNavBarTo:-(frame.size.height - kMacro1)];
}
}
- (void)animateNavBarTo:(CGFloat)y
{
[UIView animateWithDuration:0.2 animations:^{
CGRect frame = self.navigationController.navigationBar.frame;
CGFloat alpha = (frame.origin.y >= y ? 0 : 1);
frame.origin.y = y;
[self.navigationController.navigationBar setFrame:frame];
[self updateBarButtonItems:alpha];
}];
}

Animating UINavigationBar in iOS 7 (like Safari)

When scrolling content in Safari, the title bar animates to a smaller version of its self. What is the best way to implement this?
Currently, I am changing the size of the frame like so:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//
// Table view
//
CGFloat currentPosition = scrollView.contentOffset.y - CGRectGetHeight(self.tableView.tableHeaderView.frame) + CGRectGetHeight(self.navigationController.navigationBar.frame) + CGRectGetHeight([[UIApplication sharedApplication] statusBarFrame]);
if ([scrollView isKindOfClass:[HeadlinesHeadlinesTableView class]]) {
ScrollDirection scrollDirection;
if (self.lastContentOffset > scrollView.contentOffset.y) {
scrollDirection = ScrollDirectionDown;
} else if (self.lastContentOffset < scrollView.contentOffset.y) {
scrollDirection = ScrollDirectionUp;
}
self.lastContentOffset = scrollView.contentOffset.y;
CGRect frame = self.navigationController.navigationBar.frame;
CGFloat minimumFrameHeight = 30.0f;
CGFloat maximumFrameHeight = 44.0f;
CGFloat titleSize = [[self.navigationController.navigationBar.titleTextAttributes objectForKey:NSFontAttributeName] pointSize];
CGFloat minimumTitleHeight = 22.0f;
CGFloat maximumTitleHeight = 30.0f;
if (currentPosition > 0 && CGRectGetHeight(frame) >= minimumFrameHeight && CGRectGetHeight(frame) <= maximumFrameHeight) {
switch (scrollDirection) {
case ScrollDirectionUp:
frame.size.height--;
titleSize--;
break;
case ScrollDirectionDown:
frame.size.height++;
titleSize++;
break;
default:
break;
}
if (CGRectGetHeight(frame) <= minimumFrameHeight) {
frame.size.height = minimumFrameHeight;
}
if (CGRectGetHeight(frame) >= maximumFrameHeight) {
frame.size.height = maximumFrameHeight;
}
if (titleSize <= minimumTitleHeight) {
titleSize = minimumTitleHeight;
}
if (titleSize >= maximumTitleHeight) {
titleSize = maximumTitleHeight;
}
}
[self.navigationController.navigationBar setFrame:frame];
[self.navigationController.navigationBar setTitleTextAttributes: #{ NSFontAttributeName : [UIFont fontWithName:#"Canterbury-Regular" size:titleSize] }];
}
}
Naturally, this way is not smooth and a lot of code, not to mention the fact that I need to fade out bar button items as well.
Thanks in advance!
Take a look at AMScrollingNavbar, it already has fading support.
You can try to change font size of NavigationBar to make the title smaller.

How to set scrollView with minimal scale at start?

I have to set UIScrollView at start with 0.28 scale (current scale). How can i do it correclty?
Now to scaling I'm using this methods:
#pragma mark - UIScrollViewDelegate Methods
-(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return imageView;
}
-(void)scrollViewDidZoom:(UIScrollView *)scrollView {
[self centerScrollViewContent];
}
-(void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
}
- (void)centerScrollViewContent {
CGSize boundsSize = self.scrollView.bounds.size;
CGRect contentsFrame = imageView.frame;
if (contentsFrame.size.width < boundsSize.width) {
contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0f;
} else {
contentsFrame.origin.x = 0.0f;
}
if (contentsFrame.size.height < boundsSize.height) {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0f;
} else {
contentsFrame.origin.y = 0.0f;
}
imageView.frame = contentsFrame;
}
After your scrollView content is loaded, you can set the zoom scale with:
self.scrollView.zoomScale = 0.28;

Multiple images/buttons staying relative to their position on a zoomable image

I've made a map with UIScrolView, I want to place other small images or buttons onto the map and have them be in a relative position on the map when you zoom in and be able to click on them whenever. So when zoomed out, a button on country A, will still be on Country A when zoomed in, and disappear out of the screen when you scroll out of the countries view whilst zoomed in. How could I go about doing this?
As i can understand, you want to place custom views on your own custom map. And you need to keep the same sizes for views, but they should move when you scroll or zoom imageView.
You have to place views to scrollView's superview and recalculate positions when you zoom or scroll:
CustomMapViewController.h:
#interface CustomMapViewController : UIViewController <UIScrollViewDelegate>
{
UIScrollView *_scrollView;
UIImageView *_mapImageView;
NSArray *_customViews;
}
CustomMapViewController.m:
#import "CustomMapViewController.h"
enum {
kAddContactButton = 1,
kInfoDarkButton,
kInfoLightButton,
kLogoImage,
};
#implementation CustomMapViewController
- (void)dealloc
{
[_scrollView release]; _scrollView = nil;
[_mapImageView release]; _mapImageView = nil;
[_customViews release]; _customViews = nil;
[super dealloc];
}
- (void) loadView
{
[super loadView];
UIImageView *mapImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"map.png"]];
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
scrollView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
scrollView.delegate = self;
scrollView.minimumZoomScale = 0.2;
scrollView.maximumZoomScale = 2.0;
[scrollView addSubview:mapImageView];
scrollView.contentSize = mapImageView.frame.size;
[self.view addSubview:scrollView];
_scrollView = scrollView;
_mapImageView = mapImageView;
// Add custom views
UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeContactAdd];
btn1.tag = kAddContactButton;
[self.view addSubview:btn1];
UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeInfoDark];
btn2.tag = kInfoDarkButton;
[self.view addSubview:btn2];
UIButton *btn3 = [UIButton buttonWithType:UIButtonTypeInfoLight];
btn3.tag = kInfoLightButton;
[self.view addSubview:btn3];
UIImageView *image = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"logo.png"]] autorelease];
image.tag = kLogoImage;
[self.view addSubview:image];
_customViews = [[NSArray alloc] initWithObjects:btn1, btn2, btn3, image, nil];
[self _zoomToFit];
}
- (void) _zoomToFit
{
UIScrollView *scrollView = _scrollView;
CGFloat contentWidth = scrollView.contentSize.width;
CGFloat contentHeigth = scrollView.contentSize.height;
CGFloat viewWidth = scrollView.frame.size.width;
CGFloat viewHeight = scrollView.frame.size.height;
CGFloat width = viewWidth / contentWidth;
CGFloat heigth = viewHeight / contentHeigth;
CGFloat scale = MIN(width, heigth); // to fit
// CGFloat scale = MAX(width, heigth); // to fill
// May be should add something like this
if ( scale < _scrollView.minimumZoomScale ) {
_scrollView.minimumZoomScale = scale;
} else if ( scale > _scrollView.maximumZoomScale ) {
_scrollView.maximumZoomScale = scale;
}
_scrollView.zoomScale = scale;
}
////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - Positions
- (void) _updatePositionForViews:(NSArray *)views
{
CGFloat scale = _scrollView.zoomScale;
CGPoint contentOffset = _scrollView.contentOffset;
for ( UIView *view in views ) {
CGPoint basePosition = [self _basePositionForView:view];
[self _updatePositionForView:view scale:scale basePosition:basePosition offset:contentOffset];
}
}
- (CGPoint) _basePositionForView:(UIView *)view
{
switch (view.tag) {
case kAddContactButton:
return CGPointMake(50.0, 50.0);
case kInfoDarkButton:
return CGPointMake(250.0, 250.0);
case kInfoLightButton:
return CGPointMake(450.0, 250.0);
case kLogoImage:
return CGPointMake(650.0, 450.0);
default:
return CGPointZero;
}
}
- (void) _updatePositionForView:(UIView *)view scale:(CGFloat)scale basePosition:(CGPoint)basePosition offset:(CGPoint)offset;
{
CGPoint position;
position.x = (basePosition.x * scale) - offset.x;
position.y = (basePosition.y * scale) - offset.y;
CGRect frame = view.frame;
frame.origin = position;
view.frame = frame;
}
//////////////////////////////////////////////////////////////////////////////////////
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
[self _updatePositionForViews:_customViews];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self _updatePositionForViews:_customViews];
}
- (UIView *) viewForZoomingInScrollView:(UIScrollView *)scrollView;
{
return _mapImageView;
}
#end