UIScrollView с центрированным UIImageView, например, приложение "Фотографии"
Я хотел бы иметь вид прокрутки с представлением содержимого изображения. Изображение на самом деле является картой, которая намного больше, чем экран. Сначала карта должна находиться в центре прокрутки, например, фотографии в приложении "Фотографии", когда вы поворачиваете iPhone в альбомную ориентацию.
![alt text]()
Мне не удалось получить карту в центре с правильным масштабированием и прокруткой в одно и то же время.
При условии, что изображение карты начинается с верхней части экрана (в портретной ориентации), код выглядит примерно так:
- (void)loadView {
mapView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"map.jpg"]];
CGFloat mapHeight = MAP_HEIGHT * SCREEN_WIDTH / MAP_WIDTH;
mapView.frame = CGRectMake(0, 0, SCREEN_WIDTH, mapHeight);
scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)];
scrollView.delegate = self;
scrollView.contentSize = mapView.frame.size;
scrollView.maximumZoomScale = MAP_WIDTH / SCREEN_WIDTH;
scrollView.minimumZoomScale = 1;
[scrollView addSubview:mapView];
self.view = scrollView;
}
Когда я перемещаю рамку изображения в центр, изображение увеличивается только с верхней части его рамки. Я попытался поиграть с преобразованием mapView с динамически меняющимся кадром изображения. Ничто не работает для меня до сих пор.
Ответы
Ответ 1
Этот код должен работать в большинстве версий iOS (и был протестирован для работы над 3.1 вверх).
Он основан на коде Apple WWDC, упомянутом в ответе Джона.
Добавьте ниже в свой подкласс UIScrollView и замените tileContainerView на представление, содержащее ваше изображение или фрагменты:
- (void)layoutSubviews {
[super layoutSubviews];
// center the image as it becomes smaller than the size of the screen
CGSize boundsSize = self.bounds.size;
CGRect frameToCenter = tileContainerView.frame;
// center horizontally
if (frameToCenter.size.width < boundsSize.width)
frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
else
frameToCenter.origin.x = 0;
// center vertically
if (frameToCenter.size.height < boundsSize.height)
frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
else
frameToCenter.origin.y = 0;
tileContainerView.frame = frameToCenter;
}
Ответ 2
Вот что я бы подумал, решение, как в нем, ведет себя точно так же, как приложение для фотогалереи apple. Я использовал решения, которые использовали:
-(void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
но мне не понравилось это решение, потому что после того, как было сделано масштабирование, он бы отскочил, а затем быстро "прыгнул" в центр, который был очень несексуальным. Оказывается, если вы в значительной степени выполняете ту же логику, но в этой функции делегата:
-(void)scrollViewDidZoom:(UIScrollView *)pScrollView
он начинает центрировать, и когда вы уменьшаете масштаб, он центрируется:
-(void)scrollViewDidZoom:(UIScrollView *)pScrollView {
CGRect innerFrame = imageView.frame;
CGRect scrollerBounds = pScrollView.bounds;
if ( ( innerFrame.size.width < scrollerBounds.size.width ) || ( innerFrame.size.height < scrollerBounds.size.height ) )
{
CGFloat tempx = imageView.center.x - ( scrollerBounds.size.width / 2 );
CGFloat tempy = imageView.center.y - ( scrollerBounds.size.height / 2 );
CGPoint myScrollViewOffset = CGPointMake( tempx, tempy);
pScrollView.contentOffset = myScrollViewOffset;
}
UIEdgeInsets anEdgeInset = { 0, 0, 0, 0};
if ( scrollerBounds.size.width > innerFrame.size.width )
{
anEdgeInset.left = (scrollerBounds.size.width - innerFrame.size.width) / 2;
anEdgeInset.right = -anEdgeInset.left; // I don't know why this needs to be negative, but that what works
}
if ( scrollerBounds.size.height > innerFrame.size.height )
{
anEdgeInset.top = (scrollerBounds.size.height - innerFrame.size.height) / 2;
anEdgeInset.bottom = -anEdgeInset.top; // I don't know why this needs to be negative, but that what works
}
pScrollView.contentInset = anEdgeInset;
}
Где 'imageView' - это UIImageView
, который вы используете.
Ответ 3
Apple выпустила видеозапись сессии WWDC 2010 года всем участникам программы разработки iphone. Одна из обсуждаемых тем - как они создали приложение для фотографий!!! Они создают очень похожее приложение шаг за шагом и сделали весь код доступным бесплатно.
Он также не использует private api. Я не могу указать какой-либо код здесь из-за соглашения о неразглашении, но вот ссылка на загрузку кода примера. Вам, вероятно, потребуется войти в систему, чтобы получить доступ.
http://connect.apple.com/cgi-bin/WebObjects/MemberSite.woa/wa/getSoftware?code=y&source=x&bundleID=20645
И вот ссылка на страницу WWDC iTunes:
http://insideapple.apple.com/redir/cbx-cgi.do?v=2&la=en&lc=&a=kGSol9sgPHP%2BtlWtLp%2BEP%2FnxnZarjWJglPBZRHd3oDbACudP51JNGS8KlsFgxZto9X%2BTsnqSbeUSWX0doe%2Fzv%2FN5XV55%2FomsyfRgFBysOnIVggO%2Fn2p%2BiweDK%2F%2FmsIXj
Ответ 4
Я подозреваю, что вам нужно установить UIScrollView
contentOffset
.
Ответ 5
Хотелось бы, чтобы все было так просто. Я провел некоторое исследование в сети и обнаружил, что это не только моя проблема, но многие люди борются с той же проблемой не только на iPhone, но и на рабочем столе Apple Cocoa.
См. Следующие ссылки:
http://www.iphonedevsdk.com/forum/iphone-sdk-development/5740-uiimageview-uiscrollview.html
Описанное решение основано на свойстве UIViewContentModeScaleAspectFit изображения, но, к сожалению, оно не очень хорошо работает. Изображение центрировано и растет правильно, но область подпрыгивания кажется намного больше, чем изображение.
Этот парень тоже не получил ответа:
http://discussions.apple.com/thread.jspa?messageID=8322675
И, наконец, та же проблема на рабочем столе Apple Cocoa:
http://www.cocoadev.com/index.pl?CenteringInsideNSScrollView
Я предполагаю, что решение работает, но оно основано на NSClipView, который не находится на iPhone...
У кого-нибудь есть решение, работающее на iPhone?
Ответ 6
Это сработало для меня:
- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
CGFloat tempx = view.center.x-160;
CGFloat tempy = view.center.y-160;
myScrollViewOffset = CGPointMake(tempx,tempy);
}
где 160 - половина ширины/высоты вашего UIScrollView
.
Затем я установил contentoffset на тот, который был захвачен здесь.
Ответ 7
Примечание: этот метод работает. если изображение меньше, чем изображение, оно будет прокручиваться частично с экрана. Не очень важно, но и не так хорошо, как приложение для фотографий.
Во-первых, важно понять, что мы имеем дело с 2-мя представлениями, изображением с изображением в нем и прокруткой с изображением в нем.
Итак, сначала установите изображение в размере экрана:
[myImageView setFrame:self.view.frame];
Затем центрируйте изображение в изображении:
myImageView.contentMode = UIViewContentModeCenter;
Здесь весь мой код:
- (void)viewDidLoad {
AppDelegate *appDelegate = (pAppDelegate *)[[UIApplication sharedApplication] delegate];
[super viewDidLoad];
NSString *Path = [[NSBundle mainBundle] bundlePath];
NSString *ImagePath = [Path stringByAppendingPathComponent:(@"data: %@", appDelegate.MainImageName)];
UIImage *tempImg = [[UIImage alloc] initWithContentsOfFile:ImagePath];
[imgView setImage:tempImg];
myScrollView = [[UIScrollView alloc] initWithFrame:[[self view] bounds]];
[myScrollView addSubview:myImageView];
//Set ScrollView Appearance
[myScrollView setBackgroundColor:[UIColor blackColor]];
myScrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
//Set Scrolling Prefs
myScrollView.bounces = YES;
myScrollView.delegate = self;
myScrollView.clipsToBounds = YES; // default is NO, we want to restrict drawing within our scrollview
[myScrollView setCanCancelContentTouches:NO];
[myScrollView setScrollEnabled:YES];
//Set Zooming Prefs
myScrollView.maximumZoomScale = 3.0;
myScrollView.minimumZoomScale = CGImageGetWidth(tempImg.CGImage)/320;
myScrollView.zoomScale = 1.01; //Added the .01 to enable scrolling immediately upon view load.
myScrollView.bouncesZoom = YES;
[myImageView setFrame:self.view.frame];//rect];// .frame.size.height = imageHeight;
myImageView.contentMode = UIViewContentModeCenter;
self.view = myScrollView;
[tempImg release];
}
Ответ 8
Хорошо, я думаю, что нашел довольно хорошее решение этой проблемы. Фокус в том, чтобы постоянно корректировать рамку imageView. Я считаю, что это работает намного лучше, чем постоянная настройка contentInsets или contentOffSets. Мне пришлось добавить немного дополнительного кода для размещения как портретных, так и пейзажных изображений.
Здесь код:
- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
CGSize screenSize = [[self view] bounds].size;
if (myScrollView.zoomScale <= initialZoom +0.01) //This resolves a problem with the code not working correctly when zooming all the way out.
{
imageView.frame = [[self view] bounds];
[myScrollView setZoomScale:myScrollView.zoomScale +0.01];
}
if (myScrollView.zoomScale > initialZoom)
{
if (CGImageGetWidth(temporaryImage.CGImage) > CGImageGetHeight(temporaryImage.CGImage)) //If the image is wider than tall, do the following...
{
if (screenSize.height >= CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the height of the screen is greater than the zoomed height of the image do the following...
{
imageView.frame = CGRectMake(0, 0, 320*(myScrollView.zoomScale), 368);
}
if (screenSize.height < CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the height of the screen is less than the zoomed height of the image do the following...
{
imageView.frame = CGRectMake(0, 0, 320*(myScrollView.zoomScale), CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]);
}
}
if (CGImageGetWidth(temporaryImage.CGImage) < CGImageGetHeight(temporaryImage.CGImage)) //If the image is taller than wide, do the following...
{
CGFloat portraitHeight;
if (CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale] < 368)
{ portraitHeight = 368;}
else {portraitHeight = CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale];}
if (screenSize.width >= CGImageGetWidth(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the width of the screen is greater than the zoomed width of the image do the following...
{
imageView.frame = CGRectMake(0, 0, 320, portraitHeight);
}
if (screenSize.width < CGImageGetWidth (temporaryImage.CGImage) * [myScrollView zoomScale]) //If the width of the screen is less than the zoomed width of the image do the following...
{
imageView.frame = CGRectMake(0, 0, CGImageGetWidth(temporaryImage.CGImage) * [myScrollView zoomScale], portraitHeight);
}
}
[myScrollView setZoomScale:myScrollView.zoomScale -0.01];
}
Ответ 9
Один из элегантных способов сосредоточиться на содержании UISCrollView
заключается в этом.
Добавьте одного наблюдателя в contentSize вашего UISCrollView
, поэтому этот метод будет вызываться каждый раз при изменении содержимого...
[myScrollView addObserver:delegate
forKeyPath:@"contentSize"
options:(NSKeyValueObservingOptionNew)
context:NULL];
Теперь по вашему методу наблюдателя:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
// Correct Object Class.
UIScrollView *pointer = object;
// Calculate Center.
CGFloat topCorrect = ([pointer bounds].size.height - [pointer viewWithTag:100].bounds.size.height * [pointer zoomScale]) / 2.0 ;
topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );
topCorrect = topCorrect - ( pointer.frame.origin.y - imageGallery.frame.origin.y );
// Apply Correct Center.
pointer.center = CGPointMake(pointer.center.x,
pointer.center.y + topCorrect ); }
Это будет корректировать центр содержимого каждый раз при изменении его размера.
ПРИМЕЧАНИЕ. Единственный способ, которым этот контент не работает очень хорошо, - это стандартная функция масштабирования UISCrollView
.
Ответ 10
В Monotouch, который работал у меня.
this._scroll.ScrollRectToVisible(new RectangleF(_scroll.ContentSize.Width/2, _scroll.ContentSize.Height/2,1,1),false);
Ответ 11
Здесь альтернативное решение, аналогичное @JosephH, но это учитывает фактические размеры изображения. Таким образом, когда пользователь панорамирует/масштабирует, у вас никогда не будет больше пробелов на экране, чем требуется. Это обычная проблема, например, при отображении пейзажного изображения на экране портрета. Там будет пробел выше и ниже изображения, когда все изображение будет на экране (Aspect Fit). Затем, при масштабировании, другие решения рассматривают это пробелы как часть изображения, потому что это в imageView. Они позволят вам панорамировать большую часть изображения с экрана, оставляя видимыми пробелы. Это выглядит плохо для пользователя.
С этим классом вам нужно передать ему изображение, в котором он работает. У меня возникло соблазн обнаружить автоматическое обнаружение, но это быстрее, и вам нужна вся скорость, которую вы можете получить в методе layoutSubviews
.
Примечание. Как и для этого, для режима scrollView это не требуется.
//
// CentringScrollView.swift
// Cerebral Gardens
//
// Created by Dave Wood
// Copyright © 2016 Cerebral Gardens Inc. All rights reserved.
//
import UIKit
class CentringScrollView: UIScrollView {
var imageView: UIImageView?
override func layoutSubviews() {
super.layoutSubviews()
guard let superview = superview else { return }
guard let imageView = imageView else { return }
guard let image = imageView.image else { return }
var frameToCentre = imageView.frame
let imageWidth = image.size.width
let imageHeight = image.size.height
let widthRatio = superview.bounds.size.width / imageWidth
let heightRatio = superview.bounds.size.height / imageHeight
let minRatio = min(widthRatio, heightRatio, 1.0)
let effectiveImageWidth = minRatio * imageWidth * zoomScale
let effectiveImageHeight = minRatio * imageHeight * zoomScale
contentSize = CGSize(width: max(effectiveImageWidth, bounds.size.width), height: max(effectiveImageHeight, bounds.size.height))
frameToCentre.origin.x = (contentSize.width - frameToCentre.size.width) / 2
frameToCentre.origin.y = (contentSize.height - frameToCentre.size.height) / 2
imageView.frame = frameToCentre
}
}