Zoom & Pan in UIImageView inside UIScrollView - uiscrollview

I have a UIScrollView filled with UIImageView. The UIScrollView are paging enabled to let the users to "flip" those images. Each UIImageView has UIPinchGestureRecognizer for pinch zoom, and UIPanGestureRecognizer for to pan that image when zoomed in.
As probably you have noticed, what I'd like to achieve is just like what iBooks does in its application.
However, I have difficult times to get this work.
In my "BookPageViewController", I have set up UIScrollView, then fill them with images from the folder based on data (page numbers, file names, etc) from sqlite.
_pageScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
NSUInteger pageCount = [pages count];
for (int i = 0; i < pageCount; i++) {
CGFloat x = i * self.view.frame.size.width;
// Page Settings
UIImageView *pageView = [[UIImageView alloc] initWithFrame:CGRectMake(x, 0, self.view.frame.size.width, self.view.frame.size.height)];
pageView.backgroundColor = [UIColor greenColor];
pageView.image = [pages objectAtIndex:i];
pageView.userInteractionEnabled = YES;
pageView.tag = i;
// Gesture Reocgnisers
UIPinchGestureRecognizer *pinchGr = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(pinchImage:)];
pinchGr.delegate = self;
[pageView addGestureRecognizer:pinchGr];
UIPanGestureRecognizer *panGr = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panImage:)];
[pageView addGestureRecognizer:panGr];
// Finally add this page to the ScrollView
[_pageScrollView addSubview:pageView];
}
_pageScrollView.contentSize = CGSizeMake(self.view.frame.size.width * pageCount, self.view.frame.size.height);
_pageScrollView.pagingEnabled = YES;
_pageScrollView.delegate = self;
[self.view addSubview:_pageScrollView];
And with the help of another good questions here in Stackoverflow, I have put those:
- (void) pinchImage:(UIPinchGestureRecognizer*)pgr {
[self adjustAnchorPointForGestureRecognizer:pgr];
if ([pgr state] == UIGestureRecognizerStateBegan || [pgr state] == UIGestureRecognizerStateChanged) {
if ([pgr scale]) {
NSLog(#"SCALE: %f", [pgr scale]);
[pgr view].transform = CGAffineTransformScale([[pgr view] transform], [pgr scale], [pgr scale]);
[pgr setScale:1];
} else {
NSLog(#"[PAMPHLET PAGE]: The image cannot be scaled.");
}
}
}
My problem is, when I zoom in the one of the UIImageView with Pinch Gesture, the image exceeds (go over) to the next image and hides it. I believe that there should be the way to "limit" the zoom in/out and the size of UIImageView (not UIImage itself), but I don't know where to go from here. I have also put a code to limit the scale something like:
([pgr scale] > 1.0f && [pgr scale] < 1.014719) || ([pgr scale] < 1.0f && [pgr scale] > 0.98f)
but it didn't work...
I know it's not hard for ios professionals, but I'm quite new to objective-c, and this is my first time to develop real application. If this is not a good practice, I also would like to know any ideas to achieve just how to do this like ibooks do (e.g. put UIImageView in UIScrollView, then put the UIScrollView to another UIScrollView)...
Sorry for another beginner question, but I really need help.
Thanks in advance.

Without trying it out myself, my first guess would be that the transform on the UIImageView also transforms its frame. One way to solve that, would be to put the UIImageView in another UIView, and put that UIView in the UIScrollView. Your gesture recognizers and the transform would still be on the UIImageView. Make sure the UIView's clipsToBounds property is set to YES.

Related

xcode UITapGestureRecognizer on scrollview not calling until second tap

I have the following code to dismiss the keyboard if the user taps the background. It works fine if the scrollview is in the PointZero position, but if the user scrolls the view and then selects the textview, it doesn't call the "dismissKeyboard' method until the 2nd background tap.
On the first tap (for some reason) moves the scrollview offset to align with the scrollview frame to the screen bottom. The second tap will dismiss the keyboard and run the code below. I know it has to do with the scrollview. Any help would be appreciated.
Thanks
- (void)viewDidLoad {
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismissKeyboard)];
tapGesture.cancelsTouchesInView = NO;
[_scrollView addGestureRecognizer:tapGesture];
}
-(void)dismissKeyboard {
[self.view endEditing:YES];
}
- (void)keyboardWasShown:(NSNotification *)notification {
scrollViewRect = _scrollView.contentOffset.y;
NSDictionary* info = [notification userInfo];
CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
keyboardSize.height += 10;
CGFloat viewBottom = CGRectGetMaxY(self.scrollView.frame);
if ([_itemNotes isFirstResponder]) {
CGFloat notesBottom = CGRectGetMaxY(_itemNotes.frame);
viewBottom -= notesBottom;
if (viewBottom < keyboardSize.height) {
keyboardSize.height -= viewBottom;
CGPoint scrollPoint = CGPointMake(0.0, keyboardSize.height);
[self.scrollView setContentOffset:scrollPoint animated:YES];
}
else {
[self.scrollView setContentOffset:CGPointZero animated:YES];
}
}
else {
[self.scrollView setContentOffset:CGPointZero animated:YES];
}
}
- (void)keyboardWillBeHidden:(NSNotification *)notification {
CGPoint scrollPoint = CGPointMake(0.0, scrollViewRect);
[self.scrollView setContentOffset:scrollPoint animated:YES];
}
EDIT:
So I figured out a solution but it seems like there must be a better way to handle this. The problem was because I was setting the contentOffset of the scrollView so that the contentSize was beyond the screen boundaries. Thus the first tap was moving the scrollView contentOffset back within the screen boundaries and the second was performing the tap gesture. I will post my solution below hoping that someone has a better answer.
I would recommend setting
_scrollView.layer.borderColor = [UIColor redColor].CGColor;
_scrollView.layer.borderWidth = 1;
This will show you exactly where your scrollview boundaries are, which may not be where you think they are, or may be covered by something else. Also, when I open the keyboard, I generally set the scrollview frame bottom to the top of the keyboard. Otherwise, you may have content below the keyboard you can't get to. Not sure if this is exactly related to your issues.
I am assuming there must be a better solution to this but I was able to solve the issue by extending the contentSize when the keyboard is displayed and then shrinking it back down when the keyboard is hidden.
Set a float (scrollViewHeight) to hold the original content size for the reset.
//add this right before setting the content offset
scrollViewHeight = _scrollView.contentSize.height;
_scrollView.contentSize = CGSizeMake(_scrollView.frame.size.width , scrollViewHeight + keyboardSize.height);
//add this right before reseting the content offset
_scrollView.contentSize = CGSizeMake(_scrollView.frame.size.width , scrollViewHeight);
It really seems like there must be a better way that I'm not aware of. I will have to go back through the documentation to see if there is another way.

PinthToZoom and Pan of UIView with multiple images

I'm developing an iphone application with an UIView that have an image as a background and multiple buttons with image as background. The image is an image of a country and the buttons are the regions/states of that country.
The problem is that the screen is too much little to display all this data, so I want to enable pinch-to-zoom of the UIView which allows user to have a better view of the country.
Inside 'viewDiDLoad' of my UIViewController, I added:
UIPinchGestureRecognizer *twoFingerPinch = [[[UIPinchGestureRecognizer alloc]
initWithTarget:self
action:#selector(twoFingerPinch:)]
autorelease];
[[self view] addGestureRecognizer:twoFingerPinch];
and I added twoFingerPinch method:
- (void)twoFingerPinch:(UIPinchGestureRecognizer *)recognizer {
if (recognizer.scale > minValue){
CGAffineTransform transform = CGAffineTransformMakeScale(recognizer.scale, recognizer.scale);
self.view.transform = transform;
}
}
and it work correctly, the problem is that the UIView becomes bigger but user can't navigate inside.
So I switched my UIView to a UIScrollView and inside 'twoFingerPinch' method I added:
if (recognizer.state == UIGestureRecognizerStateEnded){
float scaleForView = mLastScale;
UIScrollView *tempScrollView=(UIScrollView *)self.view;
int newWidth = self.view.frame.size.width * scaleForView;
int newHeight = self.view.frame.size.height * scaleForView;
tempScrollView.contentSize=CGSizeMake(newWidth,newHeight);
mLastScale = 1.0;
}
but it doesn't work well: the UISrollView becomes much bigger but it's not bigger as the image scaled and also is not centered where pinchToZoom started.
How can i solve this problem?
Thank You

CGGradient isn't visible (not using interface builder) and UIButtons can't be triggered

I have created a view that contains a CGGradient:
// Bar ContextRef
CGRect bar = CGRectMake(0, screenHeight-staffAlignment, screenWidth, barWidth);
CGContextRef barContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(barContext);
CGContextClipToRect(barContext,bar);
// Bar GradientRef
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat components[16] = { 1.0,1.0,1.0,0.0, 0.0,0.0,0.0,1.0, 0.0,0.0,0.0,1.0, 1.0,1.0,1.0,0.0};
CGFloat locations[4] = {0.95,0.85,0.15,0.05};
size_t count = 4;
CGGradientRef gradientRef = CGGradientCreateWithColorComponents(colorSpace, components, locations, count);
// Draw Bar
CGPoint startPoint = {0.0,0.0};
CGPoint endPoint = {screenWidth,0.0};
CGContextDrawLinearGradient(barContext, gradientRef, startPoint, endPoint, 0);
CGContextRestoreGState(barContext);
This code is called in the drawRect method of the UIView. I then use a UIViewController to access the created view.
- (void)loadView {
MainPageView *mpView = [[MainPageView alloc] initWithFrame:[window bounds]];
[self setView:mpView];
[mpView release];
}
and displayed on the screen through the appDelegate:
mpViewController = [[MainPageViewController alloc] init];
[window addSubview:[mpViewController view]];
[window makeKeyAndVisible];
The UIView contains more objects, such as UIButtons, that are visible. I am assuming because they are added as a subview. But I can't work out how to add the CGGradient as a subview? Does it need to be? Is there another reason CGGradient is not visible?
I also don't get the functionality on the UIButtons. I guess that is because of where I have added the UIButtons to the view. Do the buttons need to be added in the UIViewController or the appDelegate to have functionality. Sorry to ask what would seem like simple questions but I am trying to accomplish the programming without the Interface Builder and material on that is scarce. If anyone could point me in the right direction on both these problems I would really appreciate it.
Thanks!
The functionality on the buttons was lost because the frame was too large but the buttons were still visible because the background was clearColor

Setting a nsview size

I'm working on the GUI of my application. I have a main view embedded into a NSScrollView. Its content is refreshed when the user clicks a button. It then determines the required height for displaying the content. The view is not displayed well, there are a lot of glitches, of items displayed twice... I know the problem is when I set the view size with [self setBounds: ...]. Do you know how I can change the view size? Here is code:
CGFloat allHeight= 0.0;
for(int i=0; i < [publications count]; i++) {
Publication* pub= [publications objectAtIndex:i];
CGFloat pubHeight= [self heightForTextStorage:[pub statusContent] withWidth:300];
if (pubHeight < 100.0)
pubHeight= 100.0;
allHeight += pubHeight;
}
if (allHeight > [[self superview] bounds].size.height) {
NSRect allBounds= self.frame;
allBounds.size.height= allHeight;
[self setFrame:allBounds];
}
else {
NSRect allBounds= [[self superview] bounds];
[self setFrame:allBounds];
}
Thanks in advance!
Once again I found the solution. I was changing the nsview bounds inside the drawRect method (although it was before doing any drawing). I put that piece of code elsewhere and now it's perfectly working.

Copying the drawn contents of one UIView to another

I'd like to take a UITextView and allow the user to enter text into it and then trigger a copy of the contents onto a quartz bitmap context. Does anyone know how I can perform this copy action? Should I override the drawRect method and call [super drawRect] and then take the resulting context and copy it? If so, does anyone have any reference to sample code to copy from one context to another?
Update: from reading the link in the answer below, I put together this much to attempt to copy my UIView contents into a bitmap context, but something is still not right. I get my contents mirrored across the X axis (i.e. upside down). I tried using CGContextScaleCTM() but that seems to have no effect.
I've verified that the created UIImage from the first four lines do properly create a UIImage that isn't strangely rotated/flipped, so there is something I'm doing wrong with the later calls.
// copy contents to bitmap context
UIGraphicsBeginImageContext(mTextView.bounds.size);
[mTextView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self setNeedsDisplay];
// render the created image to the bitmap context
CGImageRef cgImage = [image CGImage];
CGContextScaleCTM(mContext, 1.0, -1.0); // doesn't seem to change result
CGContextDrawImage(mContext, CGRectMake(
mTextView.frame.origin.x,
mTextView.frame.origin.y,
[image size].width, [image size].height), cgImage);
Any suggestions?
Here is the code I used to get a UIImage of UIView:
#implementation UIView (Sreenshot)
- (UIImage *)screenshot{
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);
/* iOS 7 */
BOOL visible = !self.hidden && self.superview;
CGFloat alpha = self.alpha;
BOOL animating = self.layer.animationKeys != nil;
BOOL success = YES;
if ([self respondsToSelector:#selector(drawViewHierarchyInRect:afterScreenUpdates:)]){
//only works when visible
if (!animating && alpha == 1 && visible) {
success = [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:NO];
}else{
self.alpha = 1;
success = [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
self.alpha = alpha;
}
}
if(!success){ /* iOS 6 */
self.alpha = 1;
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
self.alpha = alpha;
}
UIImage* img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
#end
You can use in iOS 7 and later:
- (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates

Resources