CoreAnimation: Transferring animating objects to next UIViewController - iphone

I am currently using this core animation; (iOS 5, ARC)
- (void)onTimer
{
// build a view from our image
UIImageView* flakeView = [[UIImageView alloc] initWithImage:flakeImage];
// use the random() function to randomize up our flake attributes
int startX = round(random() % 320);
int endX = startX; //round(random() % 320);
double scale = 1 / round(random() % 100) + 1.0;
double speed = 1 / round(random() % 100) + 1.0;
// set the flake start position
flakeView.frame = CGRectMake(startX, -100.0, 25.0 * scale, 25.0 * scale);
flakeView.alpha = 0.8;
// put the flake in our main view
[self.view addSubview:flakeView];
[self.view sendSubviewToBack:flakeView];
[UIView beginAnimations:nil context:(__bridge void*)flakeView];
// set up how fast the flake will fall
[UIView setAnimationDuration:10 * speed];
// set the postion where flake will move to
flakeView.frame = CGRectMake(endX, 500.0, 25.0 * scale, 25.0 * scale);
// set a stop callback so we can cleanup the flake when it reaches the
// end of its animation
[UIView setAnimationDidStopSelector:#selector(onAnimationComplete:finished:context:)];
[UIView setAnimationDelegate:self];
[UIView commitAnimations];
}
- (void)onAnimationComplete:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
UIImageView *flakeView = (__bridge UIImageView*)context;
[flakeView removeFromSuperview];
flakeView = nil;
}
fire;
[NSTimer scheduledTimerWithTimeInterval:(0.3) target:self selector:#selector(onTimer) userInfo:nil repeats:YES];
With this code, there are 10s of snow flakes are presented, falling down.
With the help of this code, and some custom view transitions, I am trying to make navigation actions fluid. The problem is I cannot find the proper way to transfer these (animating) context to the next UIViewController so all the animating snow flakes will keep on going where they left, and the next UIViewController's NSTimer will fire new ones.
Any help and suggestions will be appriciated.

Maybe you can put your context in your private extension of this class.
Like:#property (nonatomic,weak)IBOutlet UIImageView*context;

I doubt if you can transfer a view that is animating to another view controller. I suspect that when you remove it from its superview, it will terminate any animations.
You would probably be better off writing code that keeps track of the views that are animating in an array. When it's time to create a new view controller, query the presentationLayer of each image view's layer and get it's position. Then remove all the snowflake views from the current view controller and pass the whole array, plus an array of positions, to the new view controller. Then in the new view controller's viewDidLoad, add the array of snowflake views at their previous positions and start them animating to their previous end positions, and start an animation timer to begin adding more snowflakes.

.h file
#interface BeanManager : NSObject <UINavigationControllerDelegate>
{
NSMutableDictionary *beanDictionary;
UIViewController *currentViewController;
UIImage *coffeebean;
NSTimer *beanTimer;
NSInteger contextTag;
BOOL isDebugging;
}
#property (nonatomic, retain) NSMutableDictionary *beanDictionary;
#property (nonatomic, retain) UIViewController *currentViewController;
#property (nonatomic) BOOL isDebugging;
+ (id)sharedManager;
- (void)invalidate;
- (void)validate;
#end
.m file
+ (id)sharedManager {
static BeanManager *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
- (id)init {
if (self = [super init]) {
coffeebean = [UIImage imageNamed:#"coffeebean"];
// assign self as navigation controllers delegate
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.navigationController setDelegate:self];
self.currentViewController = appDelegate.navigationController.visibleViewController;
beanDictionary = [[NSMutableDictionary alloc] init];
NSLog(#"%# : Allocating",[self class]);
[self validate];
}
return self;
}
- (void)dealloc {
if (isDebugging) {
NSLog(#"%#: Deallocating", [self class]);
}
}
#pragma mark - UINavigationController
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if (isDebugging) {
NSLog(#"%#: Pushing %d beans to new view",[self class], [[beanDictionary allKeys] count]);
}
for (UIImageView *animatingBeans in [beanDictionary allValues]) {
[viewController.view addSubview:animatingBeans];
[viewController.view sendSubviewToBack:animatingBeans];
}
self.currentViewController = viewController;
}
#pragma mark - Internals
- (void)validate
{
beanTimer = [NSTimer scheduledTimerWithTimeInterval:(0.3) target:self selector:#selector(onTimerWithBlock) userInfo:nil repeats:YES];
}
- (void)invalidate
{
[beanTimer invalidate];
}
- (void)onTimerWithBlock
{
// build a view from our flake image
UIImageView* beanView = [[UIImageView alloc] initWithImage:coffeebean];
// use the random() function to randomize up our flake attributes
int startX = round(random() % 320);
int endX = startX; //round(random() % 320);
double scale = 1 / round(random() % 100) + 1.0;
double speed = 1 / round(random() % 100) + 1.0;
// set the flake start position
beanView.frame = CGRectMake(startX, -100.0, 25.0 * scale, 25.0 * scale);
beanView.alpha = 0.8;
beanView.tag = contextTag;
[beanDictionary setObject:beanView forKey:[NSString stringWithFormat:#"%d",contextTag]];
if (contextTag > 1000) {
contextTag = 0;
}
contextTag++;
// put the flake in our main view
[self.currentViewController.view addSubview:beanView];
[self.currentViewController.view sendSubviewToBack:beanView];
[UIView animateWithDuration:(speed * 10) animations:^{
[beanView setFrame:CGRectMake(endX, 500.0, 25.0 * scale, 25.0 * scale)];
} completion:^(BOOL finished)
{
[beanDictionary removeObjectForKey:[NSString stringWithFormat:#"%d",beanView.tag]];
if (isDebugging) {
NSLog(#"%#: Dictionary Count %d",[self class], [[beanDictionary allKeys] count]);
}
[beanView removeFromSuperview];
}];
}
This code seemed to do the work. Just far, I am not using it as a Singleton, but if I will expand this code, it should be implemented as a singleton, just to clarify. And I am using an NSDictionary instead of an NSArray which would work just as is, again I considered I might add some features to it, so I made it an NSDictionary

Related

how do I stack the UIImage together and wavering or swing in UIKit

I would like to know how this app stacks up uiimage and wavering in UIKit.
I'm following this xcode: Using a UIImageView subclass with NSTimers
to have images dropping down in random, but how do I catch it and stack the UIImage together ?
Thanks for the reply... EDIT: for clarity
https://www.youtube.com/watch?v=2dpZCoi4xFk 40sec onwards will show how it stack on top each other and swing with accerometer
This will emulate the dropping of UIIimageView
- (void)viewDidLoad {
[super viewDidLoad];
// set the background color to something COLD
self.view.backgroundColor = [UIColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:1.0];
// load our ph image we will use the same image over and over
phImage = [UIImage imageNamed:#"placeholder.png"];
// start a timet that will fire 1 times per second
[NSTimer scheduledTimerWithTimeInterval:(1) target:self selector:#selector(onTimer) userInfo:nil repeats:YES];
}
// Timer event is called whenever the timer fires
- (void)onTimer
{
// build a view from our ph image
UIImageView* placeholderView = [[UIImageView alloc] initWithImage:phImage];
// use the random() function to randomize up our ph attributes
//int startX = round(random() % 300);
int startX = round(random() % 275);
//int endX = round(random() % 300);
int endX = startX;
double scale = 1 / round(random() % 100) + 1.0;
double speed = 1 / round(random() % 50) + 1.0;
// set the PH start position
phView.frame = CGRectMake(startX, -100.0, 25.0 * scale, 25.0 * scale);
phView.alpha = 0.25;
// put the ph in our main view
[self.view addSubview:phView];
[UIView beginAnimations:nil context:phView];
// set up how fast the ph will fall
[UIView setAnimationDuration:5 * speed];
// set the postion where ph will move to
phView.frame = CGRectMake(endX, 500.0, 25.0 * scale, 25.0 * scale);
// set a stop callback so we can cleanup the ph when it reaches the
// end of its animation
[UIView setAnimationDidStopSelector:#selector(onAnimationComplete:finished:context:)];
[UIView setAnimationDelegate:self];
[UIView commitAnimations];
}
- (void)onAnimationComplete:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
UIImageView *phView = context;
[phView removeFromSuperview];
NSLog(#"[phView retainCount] = %d",[phView retainCount]);
}
Thanks for reading and commenting.
For stacking, just make a UIView that holds all the placed images together.
UIView *stackView = [UIView new];
-(void) addView: (UIView*) view toStack: (UIView*) stackView
{
if ([stackView subviews].count > 0)
{
CGRect frame = [[[stackView subviews] lastObject] frame];
view.frame = CGRectOffset(frame, 0, 20);
}
[stackView addSubview:view];
}
For tilting, set up an accelerometer and use the x and y to determine the tilt.
-(void)configureAccelerometer
{
UIAccelerometer* theAccelerometer = [UIAccelerometer sharedAccelerometer];
theAccelerometer.updateInterval = 0.20;
theAccelerometer.delegate = self;
// Delegate events begin immediately.
}
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
UIAccelerationValue x, y, z;
x = acceleration.x;
y = acceleration.y;
//[stackView tiltForX: x Y: y]
}
More info on Motion Events
For the collision detection, you can probably do something like
if (CGRectIntersectsRect([[[stackView subviews] lastObject] frame], fallingView.frame))
[self addView: fallingView toStack: stackView];

Animate addtosubview for multiple subviews

I am having an array of UIViews and I want to arrange them in a main view in a grid form, and I also wanted it to animate ( ie the grids should appear one by one slowly). Tried animating with in a for loop but didnt work but came up with a work around to achieve something closer to what I want, but just wanted to know if there is a better way to do this. I know there is some property with CAAnimationGroup (begintime) but sure how to connect it to addtosubview. Here is my code, let me know if there is a better way to do it.
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
CGFloat x = 0;
CGFloat delay = .3;
for(int i=0;i<30;i++)
{
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(x, 0, 50, 50)];
myView.backgroundColor = [UIColor greenColor];
myView.alpha = 0.0;
x = x + 50+ 2;
[UIView animateWithDuration:0.25 delay:delay options:UIViewAnimationOptionCurveEaseInOut animations:^{
[self.view addSubview:myView];
myView.transform = CGAffineTransformMakeScale(0.5, 0.5);
// myView.transform = CGAffineTransformMakeRotation(90);
} completion:^(BOOL finished) {
[UIView animateWithDuration:.5 animations:^{
myView.transform = CGAffineTransformMakeScale(1.0, 1.0);
myView.alpha = 1.0;
}];
}];
delay = delay + .3;
}
// Do any additional setup after loading the view, typically from a nib.
}
-anoop
Hi,I have also used animation within for loop but it didn't work
properly then I call a method that animate the view , multiple times
and it worked properly.I think this is better way to simply apply
animation. by calling a method.I m putting some code here may it will
help u.
- (void)viewDidLoad
{
radius=82.097;
rotationMultiplier=1;
toAngle = fromAngle = 0;
toStart = NO;
center=CGPointMake(161, 233);
NSString *path = [[NSBundle mainBundle] pathForResource:#"click" ofType:#"mp3"];
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:nil];
[self.player prepareToPlay];
SingleTouchGesture *wheelRecogniser=[[SingleTouchGesture alloc] initWithTarget:self action:#selector(rotateWheel:)];
[self.view addGestureRecognizer:wheelRecogniser];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
-(void) rotate:(int) time
{
CABasicAnimation *topAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
topAnimation.duration=time;
topAnimation.fromValue= [NSNumber numberWithFloat:fromAngle];
topAnimation.toValue=[NSNumber numberWithFloat:toAngle];
topAnimation.delegate = self;
topAnimation.fillMode = kCAFillModeForwards;
topAnimation.removedOnCompletion = NO;
[wheelImage.layer addAnimation:topAnimation forKey:#"transform"];
fromAngle = toAngle;
}
-(void) rotateWheel:(SingleTouchGesture*) recogniser
{
toAngle += recogniser.rotation;
if (toStart)
{
if (recogniser.state == UIGestureRecognizerStateEnded)
{
NSLog(#"Speed === %f",recogniser.speed);
rotationMultiplier=[self getMultiplier:recogniser.speed];
NSLog(#"Rotation Multiplier..%f",rotationMultiplier);
toStart = NO;
NSLog(#"State Ended");
self.player.currentTime=0;
[self.player play];
if (recogniser.rotation>=0)
{
toAngle =fromAngle+(rotationMultiplier*3.14);
}
else
{
toAngle =fromAngle-(rotationMultiplier*3.14);
}
}
[self rotate:1];
}
else
{
if (recogniser.state==UIGestureRecognizerStateChanged) {
NSLog(#"continue...");
CGPoint pt=[recogniser locationInView:self.view];
float dis=[recogniser distanceBetweenPoints:center with:pt];
if (dis<radius)
{
if (recogniser.rotation>=0)
{
NSLog(#"Rotation value: +ve: %f",recogniser.rotation);
toStart = YES;
toAngle =fromAngle+(rotationMultiplier*3.14);
recogniser.rotation = 0;
}
else
{
NSLog(#"Rotation value: -ve: %f",recogniser.rotation);
toStart = YES;
toAngle=fromAngle-(rotationMultiplier*3.14);
recogniser.rotation = 0;
}
}
}
}
}
Here rotate method is calling multiple times.

How to scroll view up when keyboard appears?

I know that this question has been asked over and over again, but nothing seems to be working for me. Most of the solutions around are pretty out of date, and the rest are incredibly huge blocks of code that are ten times larger then the actual projects coding. I have a few UITextFields lined up vertically, but when the keyboard launches to edit one, it covers up the text field. I was wondering if there is a simple beginner way to scroll the view up, and then back down when the editing starts and ends?
Thank you.
I have made solutions that work with scroll and non-scroll views using keyboard notification and a detection of the current first responder, but sometimes I use this trivial solution instead: The simple way is to detect the opening keyboard via the text field delegate's textViewDidBeginEditing: method and to move the entire view up. The easiest way to do this is with something along the lines of changing self.view.bounds.origin.y to -100 (or whatever). Use the corresponding textViewShouldEndEditing: method to set it to the opposite, which is 100 in this case. Changing bounds is a relative procedure. After changing it the frame is moved but the bounds origin is still zero.
Since I found it, I use TPKeyboardAvoiding - https://github.com/michaeltyson/TPKeyboardAvoiding.
It is working great, and is very easy to setup:
Add a UIScrollView into your view controller's xib
Set the scroll view's class to TPKeyboardAvoidingScrollView (still in the xib, via the identity inspector)
Place all your controls within that scroll view
You can also create it programmatically, if you want.
There is a class for the same need inside a UITableViewController ; it is only needed in case you support a version of iOS below 4.3.
#BenLu and other users who are facing problem of the function are never getting called is because of following reason:
As the delegate inbuild function bydefaults return void instead of BOOL this is how it should be as follows:
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.35f];
CGRect frame = self.view.frame;
frame.origin.y = -100;
[self.view setFrame:frame];
[UIView commitAnimations];
}
-(void)textFieldDidEndEditing:(UITextField *)textField
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.35f];
CGRect frame = self.view.frame;
frame.origin.y = 100;
[self.view setFrame:frame];
[UIView commitAnimations];
}
I spent sometime on this problem and gathered pieces code to create one final solution. My problem was related to UITableView scrolling and keyboard open/close.
You need two partial methods in your cell class:
void EditingBegin(UITextField sender)
{
// Height of tallest cell, you can ignore this!
float tableMargin = 70.0f;
float tableHeight = _tableView.Frame.Size.Height;
float keyBoardHeight = KeyboardHeight();
NSIndexPath[] paths = this._tableView.IndexPathsForVisibleRows;
RectangleF rectLast = this._tableView.RectForSection(paths[paths.Length - 1].Section);
RectangleF rectFirst = this._tableView.RectForSection(paths[0].Section);
float lastCellY = rectLast.Y - rectFirst.Y;
if (lastCellY > tableHeight - keyBoardHeight)
{
float diff = lastCellY - (tableHeight - tableMargin - keyBoardHeight);
this._tableView.ContentInset = new UIEdgeInsets(0.0f, 0.0f, diff, 0.0f);
}
float cellPosition = this._tableView.RectForSection(this._section).Y;
if (cellPosition > tableHeight - keyBoardHeight)
{
if (this._tableView.ContentInset.Bottom == 0.0f)
{
float diff = cellPosition - (tableHeight - tableMargin - keyBoardHeight);
this._tableView.ContentInset = new UIEdgeInsets(0.0f, 0.0f, diff, 0.0f);
}
else
{
this._tableView.ScrollToRow(NSIndexPath.FromItemSection(0, this._section), UITableViewScrollPosition.Middle, true);
}
}
}
partial void EditingEnd(UITextField sender)
{
UIView.BeginAnimations(null);
UIView.SetAnimationDuration(0.3f);
this._tableView.ContentInset = new UIEdgeInsets(0.0f, 0.0f, 0.0f, 0.0f);
UIView.CommitAnimations();
}
and then in your view controller class:
public override void WillAnimateRotation(UIInterfaceOrientation toInterfaceOrientation, double duration)
{
base.WillAnimateRotation(toInterfaceOrientation, duration);
float bottom = this.TableView.ContentInset.Bottom;
if (bottom > 0.0f)
{
if (toInterfaceOrientation == UIInterfaceOrientation.Portrait || toInterfaceOrientation == UIInterfaceOrientation.PortraitUpsideDown)
{
bottom = bottom * UIScreen.MainScreen.Bounds.Width / UIScreen.MainScreen.Bounds.Height;
}
else
{
bottom = bottom * UIScreen.MainScreen.Bounds.Height / UIScreen.MainScreen.Bounds.Width;
}
UIEdgeInsets insets = this.TableView.ContentInset;
this.TableView.ContentInset = new UIEdgeInsets(0.0f, 0.0f, bottom, 0.0f);
}
}
If you have a UITableView or a UIScrollView it's better to change values for contentOffset instead of making changes to the frame.
Working on Peter's Answer, adding this method to your class works nicely:
- (void)textViewDidBeginEditing:(UITextField *)textField {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.35f];
CGPoint offset = self.tableView.contentOffset;
offset.y += 200; // You can change this, but 200 doesn't create any problems
[self.tableView setContentOffset:offset];
[UIView commitAnimations];
}
That's it, no need to add the textViewDidEndEditing method.
I shouldn't need to say this, but for this to work your UITextField or UITextView must be a delegate of your controller.
Starting with Peter's answer, I developed the following approach in Swift 3.0 under iOS 10.1. I'm doing this for a textView, so I have implemented the UITextViewDelegate functions textViewDidBeginEditing and textViewDidEndEditing where I adjust the view's bounds. As you can see, I set the origin Y value to a small positive number to scroll up and then back to 0 to return to the original position.
Here is the relevant code from my ViewController. You don't need to animate, but it adds a nice touch.
func textViewDidBeginEditing(_ textView: UITextView)
{
if UIScreen.main.bounds.height < 568 {
UIView.animate(withDuration: 0.75, animations: {
self.view.bounds.origin.y = 60
})
}
}
func textViewDidEndEditing(_ textView: UITextView)
{
if UIScreen.main.bounds.height < 568 {
UIView.animate(withDuration: 0.75, animations: {
self.view.bounds.origin.y = 0
})
}
}
i have a scrollview and 3 text fields in this. I have a simple code from my own application :
.h file is :
#import <UIKit/UIKit.h>
#interface AddContactViewController : UIViewController<UITextFieldDelegate, UIScrollViewDelegate>
#property (nonatomic, retain) NSDictionary *dict_contactDetail;
#property (nonatomic, retain) IBOutlet UILabel *lbl_name;
#property (nonatomic, retain) IBOutlet UITextField *txtField_tel;
#property (nonatomic, retain) IBOutlet UITextField *txtField_address;
#property (nonatomic, retain) IBOutlet UITextField *txtField_email;
#property (nonatomic, retain) IBOutlet UIScrollView *scrollView;
#end
.m file :
#import "AddContactViewController.h"
#interface AddContactViewController ()
#end
#implementation AddContactViewController
#synthesize dict_contactDetail;
#synthesize lbl_name, txtField_tel, txtField_email, txtField_address, scrollView;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
// NSLog(#"dict_contactDetail : %#", dict_contactDetail);
UIBarButtonItem * rightButton = [[UIBarButtonItem alloc] initWithTitle:#"Add" style:UIBarButtonSystemItemDone target:self action:#selector(addEmergencyContact:)];
self.navigationItem.rightBarButtonItem = rightButton;
lbl_name.text = [NSString stringWithFormat:#"%# %#", [dict_contactDetail valueForKey:#"fname"], [dict_contactDetail valueForKey:#"lname"]];
txtField_tel.returnKeyType = UIReturnKeyDone;
txtField_email.returnKeyType = UIReturnKeyDone;
txtField_address.returnKeyType = UIReturnKeyDone;
}
-(void)addEmergencyContact:(id)sender
{
scrollView.frame = CGRectMake(0, 0, 320, 460);
}
#pragma mark - text field delegates
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
if([textField isEqual:txtField_tel])
{
[scrollView setContentOffset:CGPointMake(0, 70)];
scrollView.frame = CGRectMake(0, 0, 320, 210);
}
if([textField isEqual:txtField_address])
{
[scrollView setContentOffset:CGPointMake(0, 140)];
scrollView.frame = CGRectMake(0, 0, 320, 210);
}
if([textField isEqual:txtField_email])
{
[scrollView setContentOffset:CGPointMake(0, 210)];
scrollView.frame = CGRectMake(0, 0, 320, 210);
}
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
scrollView.frame = CGRectMake(0, 0, 320, 460);
[textField resignFirstResponder];
return YES;
}
#end

animation with infinite scrollView

I have 5 pictures inside an infinite scrollView.
So in order to make that scrollView infinite/circular I positioned my images like this:
5 1 2 3 4 5 1
meaning: last picture first picture second picture.....last picture first picture
And in order for it to become infinite I have the following code:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
if(self.scrollView.contentOffset.x == 0){
[self.scrollView scrollRectToVisible:CGRectMake(self.view.frame.size.width*pathImages.count, 0, self.view.frame.size.width, self.view.frame.size.height) animated:NO];
}
else if (self.scrollView.contentOffset.x == self.view.frame.size.width*(pathImages.count+1)) {
[self.scrollView scrollRectToVisible:CGRectMake(self.view.frame.size.width,0 ,self.view.frame.size.width, self.view.frame.size.height) animated:NO];
}
}
which means that when I'm at the last picture the contentOffset of the scrollView is set to go to the first picture-in this way I get an infinite scrollView.
What I wanted next was for my scrollView to slide automatically-for this I set a timer which calls one method-onTimer:
- (void) onTimer{
NSLog(#"flip pages");
if(h < pathImages.count*self.view.frame.size.width)
{
h+= self.view.frame.size.width;
}
else
{
h=self.view.frame.size.width;
}
if(self.scrollView.contentOffset.x == 0){
[self.scrollView scrollRectToVisible:CGRectMake(self.view.frame.size.width*pathImages.count, 0, self.view.frame.size.width, self.view.frame.size.height) animated:NO];
}
if (self.scrollView.contentOffset.x == self.view.frame.size.width*pathImages.count)
{
[self.scrollView scrollRectToVisible:CGRectMake(self.view.frame.size.width,0 ,self.view.frame.size.width, self.view.frame.size.height) animated:NO];
}
else
[UIView animateWithDuration:1
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{ self.scrollView.contentOffset = CGPointMake(h, 0); }
completion:NULL];
}
This magic line:
[UIView animateWithDuration:1
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{ self.scrollView.contentOffset = CGPointMake(h, 0); }
completion:NULL];
does the scoll automatically with animation.
Everything is great except this:
after I view the last picture I should set the offset of scroll view in order to get back to the first picture with animation.
Well if I do that:
if (self.scrollView.contentOffset.x == self.view.frame.size.width*pathImages.count)
{
[UIView animateWithDuration:1
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{ [self.scrollView scrollRectToVisible:CGRectMake(self.view.frame.size.width,0 ,self.view.frame.size.width, self.view.frame.size.height) animated:NO]; }
completion:NULL];
}
after the last picture is viewed in order to get to the first picture...it loops through all the other pictures.
What I want is this:after I view the last picture to get me back to the first picture which should be loaded on screen using animation, but without viewing all the other pictures between them.Thanks
If I see this correctly, the problem is, that you again use the animation to scroll the view back to zero position. I believe you need to modify the last bit of code you posted to something like this:
if (self.scrollView.contentOffset.x == self.view.frame.size.width*pathImages.count)
{
[self.scrollView scrollRectToVisible:CGRectMake(self.view.frame.size.width,0 ,self.view.frame.size.width, self.view.frame.size.height) animated:NO];//no animation on returning
[self onTimer];//even if this code is already inside method "onTimer"
}
Rather then what you are doing try using a scrollview that displays only 3 to 5 images at the time (if they are fullscreen). If you will have many images in your application, it will crash because of high memory consumption. Try playing with this test example that does nearly what you want:
HEADER:
#import <Foundation/Foundation.h>
#interface IScrollView : UIScrollView <UIScrollViewDelegate> {
NSMutableArray *imagePaths;
UIImageView *imageViews[3];
NSInteger currentImage;
NSTimer *animationTimer;//weak link
}
#end
SOURCE:
#import "IScrollView.h"
#implementation IScrollView
- (UIImage *)imageFromResourcesWithName:(NSString *)name {
UIImage *ret = [[UIImage alloc] initWithContentsOfFile:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:name]];
return [ret autorelease];
}
- (id)initWithFrame:(CGRect)frame {
if((self = [super initWithFrame:frame])) {
imagePaths = [[NSMutableArray alloc] init];
[imagePaths addObject:#"imag1.png"];
[imagePaths addObject:#"imag2.png"];
[imagePaths addObject:#"imag3.png"];
[imagePaths addObject:#"imag4.png"];
[imagePaths addObject:#"imag5.png"];
imageViews[0] = [[UIImageView alloc] initWithFrame:CGRectMake(320.0f*0, .0f, 320.0f, 480.0f)];
imageViews[1] = [[UIImageView alloc] initWithFrame:CGRectMake(320.0f*1, .0f, 320.0f, 480.0f)];
imageViews[2] = [[UIImageView alloc] initWithFrame:CGRectMake(320.0f*2, .0f, 320.0f, 480.0f)];
[self addSubview:imageViews[0]];
[self addSubview:imageViews[1]];
[self addSubview:imageViews[2]];
imageViews[0].image = [self imageFromResourcesWithName:[imagePaths objectAtIndex:0]];
imageViews[1].image = [self imageFromResourcesWithName:[imagePaths objectAtIndex:1]];
imageViews[2].image = [self imageFromResourcesWithName:[imagePaths objectAtIndex:2]];
currentImage = 1;
self.contentOffset = CGPointMake(320.0f*currentImage, .0f);
self.contentSize = CGSizeMake(3.0f*320.0f, 480.0f);
self.delegate = self;
animationTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0 target:self selector:#selector(scrollFragment) userInfo:nil repeats:YES];
}
return self;
}
- (void)repositionIfNeeded {
CGFloat offsetX = self.contentOffset.x;
NSInteger iCount = [imagePaths count];
if(offsetX > 320.0f*1.75f) {
self.contentOffset = CGPointMake(offsetX-320.0f, .0f);
imageViews[0].image = imageViews[1].image;
imageViews[1].image = imageViews[2].image;
NSInteger imageToLoad = currentImage+2;
if(imageToLoad>iCount-1)
imageToLoad -= iCount;
imageViews[2].image = [self imageFromResourcesWithName:[imagePaths objectAtIndex:imageToLoad]];
currentImage++;
if(currentImage>iCount-1)
currentImage -= iCount;
}
else if(offsetX < 320.0f*.25f) {
self.contentOffset = CGPointMake(offsetX+320.0f, .0f);
imageViews[2].image = imageViews[1].image;
imageViews[1].image = imageViews[0].image;
NSInteger imageToLoad = currentImage-2;
if(imageToLoad<0)
imageToLoad += iCount;
imageViews[0].image = [self imageFromResourcesWithName:[imagePaths objectAtIndex:imageToLoad]];
currentImage--;
if(currentImage<0)
currentImage += iCount;
}
}
- (void)scrollFragment {
self.contentOffset = CGPointMake(self.contentOffset.x+1.0, .0f);
[self repositionIfNeeded];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self repositionIfNeeded];
}
- (void)dealloc {
[imageViews[0] release];
[imageViews[1] release];
[imageViews[2] release];
[imagePaths release];
}
#end

Fitting an Image to Screen on Rotation iPhone / iPad?

I have been playing around with one of the iPhone examples from Apple' web site (ScrollViewSuite) . I am trying to tweak it a bit so that when I rotate the the iPad the image will fit into the screen in landscape mode vertical. I have been successful in getting the image to rotate, but the image is larger than the height of the landscape screen, so the bottom is below the screen. I would like to image to scale to the height of the landscape screen.
I have been playing around with various autoSizingMask attributes without success.
The imageView is called "zoomView" this is the actual image which loads into a scrollView called imageScrollView.
I am trying to achieve the screen to rotate and look like this.... sorry only 1 link allowed new user :(
olsonvox.com/photos/correct.png
However, this is what My screen is looking like.
http://www.olsonvox.com/photos/incorrect.png
I would really appreciate some advice or guidance. Below is the RootViewController.m for the project.
Blade
#
import "RootViewController.h"
#define ZOOM_VIEW_TAG 100
#define ZOOM_STEP 1.5
#define THUMB_HEIGHT 150
#define THUMB_V_PADDING 25
#define THUMB_H_PADDING 25
#define CREDIT_LABEL_HEIGHT 25
#define AUTOSCROLL_THRESHOLD 30
#interface RootViewController (ViewHandlingMethods)
- (void)toggleThumbView;
- (void)pickImageNamed:(NSString *)name;
- (NSArray *)imageNames;
- (void)createThumbScrollViewIfNecessary;
- (void)createSlideUpViewIfNecessary;
#end
#interface RootViewController (AutoscrollingMethods)
- (void)maybeAutoscrollForThumb:(ThumbImageView *)thumb;
- (void)autoscrollTimerFired:(NSTimer *)timer;
- (void)legalizeAutoscrollDistance;
- (float)autoscrollDistanceForProximityToEdge:(float)proximity;
#end
#interface RootViewController (UtilityMethods)
- (CGRect)zoomRectForScale:(float)scale withCenter:(CGPoint)center;
#end
#implementation RootViewController
- (void)loadView {
[super loadView];
imageScrollView = [[UIScrollView alloc] initWithFrame:[[self view]bounds]];
// this code makes the image resize to the width and height properly.
imageScrollView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin| UIViewAutoresizingFlexibleBottomMargin| UIViewAutoresizingFlexibleBottomMargin;
// TRY SETTNG CENTER HERE SOMEHOW>....
[imageScrollView setBackgroundColor:[UIColor blackColor]];
[imageScrollView setDelegate:self];
[imageScrollView setBouncesZoom:YES];
[[self view] addSubview:imageScrollView];
[self toggleThumbView];
// intitializes with the first image.
[self pickImageNamed:#"lookbook1"];
}
- (void)dealloc {
[imageScrollView release];
[slideUpView release];
[thumbScrollView release];
[super dealloc];
}
#pragma mark UIScrollViewDelegate methods
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
UIView *view = nil;
if (scrollView == imageScrollView) {
view = [imageScrollView viewWithTag:ZOOM_VIEW_TAG];
}
return view;
}
/************************************** NOTE **************************************/
/* The following delegate method works around a known bug in zoomToRect:animated: */
/* In the next release after 3.0 this workaround will no longer be necessary */
/**********************************************************************************/
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
[scrollView setZoomScale:scale+0.01 animated:NO];
[scrollView setZoomScale:scale animated:NO];
}
#pragma mark TapDetectingImageViewDelegate methods
- (void)tapDetectingImageView:(TapDetectingImageView *)view gotSingleTapAtPoint:(CGPoint)tapPoint {
// Single tap shows or hides drawer of thumbnails.
[self toggleThumbView];
}
- (void)tapDetectingImageView:(TapDetectingImageView *)view gotDoubleTapAtPoint:(CGPoint)tapPoint {
// double tap zooms in
float newScale = [imageScrollView zoomScale] * ZOOM_STEP;
CGRect zoomRect = [self zoomRectForScale:newScale withCenter:tapPoint];
[imageScrollView zoomToRect:zoomRect animated:YES];
}
- (void)tapDetectingImageView:(TapDetectingImageView *)view gotTwoFingerTapAtPoint:(CGPoint)tapPoint {
// two-finger tap zooms out
float newScale = [imageScrollView zoomScale] / ZOOM_STEP;
CGRect zoomRect = [self zoomRectForScale:newScale withCenter:tapPoint];
[imageScrollView zoomToRect:zoomRect animated:YES];
}
#pragma mark ThumbImageViewDelegate methods
- (void)thumbImageViewWasTapped:(ThumbImageView *)tiv {
[self pickImageNamed:[tiv imageName]];
[self toggleThumbView];
}
- (void)thumbImageViewStartedTracking:(ThumbImageView *)tiv {
[thumbScrollView bringSubviewToFront:tiv];
}
// CONTROLS DRAGGING AND DROPPING THUMBNAILS...
- (void)thumbImageViewMoved:(ThumbImageView *)draggingThumb {
// check if we've moved close enough to an edge to autoscroll, or far enough away to stop autoscrolling
[self maybeAutoscrollForThumb:draggingThumb];
/* The rest of this method handles the reordering of thumbnails in the thumbScrollView. See */
/* ThumbImageView.h and ThumbImageView.m for more information about how this works. */
// we'll reorder only if the thumb is overlapping the scroll view
if (CGRectIntersectsRect([draggingThumb frame], [thumbScrollView bounds])) {
BOOL draggingRight = [draggingThumb frame].origin.x > [draggingThumb home].origin.x ? YES : NO;
/* we're going to shift over all the thumbs who live between the home of the moving thumb */
/* and the current touch location. A thumb counts as living in this area if the midpoint */
/* of its home is contained in the area. */
NSMutableArray *thumbsToShift = [[NSMutableArray alloc] init];
// get the touch location in the coordinate system of the scroll view
CGPoint touchLocation = [draggingThumb convertPoint:[draggingThumb touchLocation] toView:thumbScrollView];
// calculate minimum and maximum boundaries of the affected area
float minX = draggingRight ? CGRectGetMaxX([draggingThumb home]) : touchLocation.x;
float maxX = draggingRight ? touchLocation.x : CGRectGetMinX([draggingThumb home]);
// iterate through thumbnails and see which ones need to move over
for (ThumbImageView *thumb in [thumbScrollView subviews]) {
// skip the thumb being dragged
if (thumb == draggingThumb) continue;
// skip non-thumb subviews of the scroll view (such as the scroll indicators)
if (! [thumb isMemberOfClass:[ThumbImageView class]]) continue;
float thumbMidpoint = CGRectGetMidX([thumb home]);
if (thumbMidpoint >= minX && thumbMidpoint <= maxX) {
[thumbsToShift addObject:thumb];
}
}
// shift over the other thumbs to make room for the dragging thumb. (if we're dragging right, they shift to the left)
float otherThumbShift = ([draggingThumb home].size.width + THUMB_H_PADDING) * (draggingRight ? -1 : 1);
// as we shift over the other thumbs, we'll calculate how much the dragging thumb's home is going to move
float draggingThumbShift = 0.0;
// send each of the shifting thumbs to its new home
for (ThumbImageView *otherThumb in thumbsToShift) {
CGRect home = [otherThumb home];
home.origin.x += otherThumbShift;
[otherThumb setHome:home];
[otherThumb goHome];
draggingThumbShift += ([otherThumb frame].size.width + THUMB_H_PADDING) * (draggingRight ? 1 : -1);
}
// change the home of the dragging thumb, but don't send it there because it's still being dragged
CGRect home = [draggingThumb home];
home.origin.x += draggingThumbShift;
[draggingThumb setHome:home];
}
}
- (void)thumbImageViewStoppedTracking:(ThumbImageView *)tiv {
// if the user lets go of the thumb image view, stop autoscrolling
[autoscrollTimer invalidate];
autoscrollTimer = nil;
}
#pragma mark Autoscrolling methods
- (void)maybeAutoscrollForThumb:(ThumbImageView *)thumb {
autoscrollDistance = 0;
// only autoscroll if the thumb is overlapping the thumbScrollView
if (CGRectIntersectsRect([thumb frame], [thumbScrollView bounds])) {
CGPoint touchLocation = [thumb convertPoint:[thumb touchLocation] toView:thumbScrollView];
float distanceFromLeftEdge = touchLocation.x - CGRectGetMinX([thumbScrollView bounds]);
float distanceFromRightEdge = CGRectGetMaxX([thumbScrollView bounds]) - touchLocation.x;
if (distanceFromLeftEdge < AUTOSCROLL_THRESHOLD) {
autoscrollDistance = [self autoscrollDistanceForProximityToEdge:distanceFromLeftEdge] * -1; // if scrolling left, distance is negative
} else if (distanceFromRightEdge < AUTOSCROLL_THRESHOLD) {
autoscrollDistance = [self autoscrollDistanceForProximityToEdge:distanceFromRightEdge];
}
}
// if no autoscrolling, stop and clear timer
if (autoscrollDistance == 0) {
[autoscrollTimer invalidate];
autoscrollTimer = nil;
}
// otherwise create and start timer (if we don't already have a timer going)
else if (autoscrollTimer == nil) {
autoscrollTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60.0)
target:self
selector:#selector(autoscrollTimerFired:)
userInfo:thumb
repeats:YES];
}
}
- (float)autoscrollDistanceForProximityToEdge:(float)proximity {
// the scroll distance grows as the proximity to the edge decreases, so that moving the thumb
// further over results in faster scrolling.
return ceilf((AUTOSCROLL_THRESHOLD - proximity) / 5.0);
}
- (void)legalizeAutoscrollDistance {
// makes sure the autoscroll distance won't result in scrolling past the content of the scroll view
float minimumLegalDistance = [thumbScrollView contentOffset].x * -1;
float maximumLegalDistance = [thumbScrollView contentSize].width - ([thumbScrollView frame].size.width + [thumbScrollView contentOffset].x);
autoscrollDistance = MAX(autoscrollDistance, minimumLegalDistance);
autoscrollDistance = MIN(autoscrollDistance, maximumLegalDistance);
}
- (void)autoscrollTimerFired:(NSTimer*)timer {
[self legalizeAutoscrollDistance];
// autoscroll by changing content offset
CGPoint contentOffset = [thumbScrollView contentOffset];
contentOffset.x += autoscrollDistance;
[thumbScrollView setContentOffset:contentOffset];
// adjust thumb position so it appears to stay still
ThumbImageView *thumb = (ThumbImageView *)[timer userInfo];
[thumb moveByOffset:CGPointMake(autoscrollDistance, 0)];
}
#pragma mark View handling methods
- (void)toggleThumbView {
[self createSlideUpViewIfNecessary]; // no-op if slideUpView has already been created
CGRect frame = [slideUpView frame];
if (thumbViewShowing) {
frame.origin.y = 0;
} else {
frame.origin.y = -225;
}
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3];
[slideUpView setFrame:frame];
[UIView commitAnimations];
thumbViewShowing = !thumbViewShowing;
}
- (void)pickImageNamed:(NSString *)name {
// first remove previous image view, if any
[[imageScrollView viewWithTag:ZOOM_VIEW_TAG] removeFromSuperview];
UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:#"%#.jpg", name]];
TapDetectingImageView *zoomView = [[TapDetectingImageView alloc] initWithImage:image];
zoomView.autoresizingMask = UIViewAutoresizingFlexibleWidth ;
[zoomView setDelegate:self];
[zoomView setTag:ZOOM_VIEW_TAG];
[imageScrollView addSubview:zoomView];
[imageScrollView setContentSize:[zoomView frame].size];
[zoomView release];
// choose minimum scale so image width fits screen
float minScale = [imageScrollView frame].size.width / [zoomView frame].size.width;
[imageScrollView setMinimumZoomScale:minScale];
[imageScrollView setZoomScale:minScale];
[imageScrollView setContentOffset:CGPointZero];
}
- (NSArray *)imageNames {
// the filenames are stored in a plist in the app bundle, so create array by reading this plist
NSString *path = [[NSBundle mainBundle] pathForResource:#"Images" ofType:#"plist"];
NSData *plistData = [NSData dataWithContentsOfFile:path];
NSString *error; NSPropertyListFormat format;
NSArray *imageNames = [NSPropertyListSerialization propertyListFromData:plistData
mutabilityOption:NSPropertyListImmutable
format:&format
errorDescription:&error];
if (!imageNames) {
NSLog(#"Failed to read image names. Error: %#", error);
[error release];
}
return imageNames;
}
- (void)createSlideUpViewIfNecessary {
if (!slideUpView) {
[self createThumbScrollViewIfNecessary];
CGRect bounds = [[self view] bounds];
float thumbHeight = [thumbScrollView frame].size.height;
float labelHeight = CREDIT_LABEL_HEIGHT;
// create label giving credit for images
UILabel *creditLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, thumbHeight, bounds.size.width, labelHeight)];
[creditLabel setBackgroundColor:[UIColor clearColor]];
[creditLabel setTextColor:[UIColor whiteColor]];
// [creditLabel setFont:[UIFont fontWithName:#"Helvetica" size:16]];
// [creditLabel setText:#"SAMPLE TEXT"];
[creditLabel setTextAlignment:UITextAlignmentCenter];
// create container view that will hold scroll view and label
CGRect frame = CGRectMake(0.0, -225.00, bounds.size.width+256, thumbHeight + labelHeight);
slideUpView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
slideUpView = [[UIView alloc] initWithFrame:frame];
[slideUpView setBackgroundColor:[UIColor blackColor]];
[slideUpView setOpaque:NO];
[slideUpView setAlpha:.75];
[[self view] addSubview:slideUpView];
// add subviews to container view
[slideUpView addSubview:thumbScrollView];
[slideUpView addSubview:creditLabel];
[creditLabel release];
}
}
- (void)createThumbScrollViewIfNecessary {
if (!thumbScrollView) {
float scrollViewHeight = THUMB_HEIGHT + THUMB_V_PADDING;
float scrollViewWidth = [[self view] bounds].size.width;
thumbScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, scrollViewWidth, scrollViewHeight)];
[thumbScrollView setCanCancelContentTouches:NO];
[thumbScrollView setClipsToBounds:NO];
// now place all the thumb views as subviews of the scroll view
// and in the course of doing so calculate the content width
float xPosition = THUMB_H_PADDING;
for (NSString *name in [self imageNames]) {
UIImage *thumbImage = [UIImage imageNamed:[NSString stringWithFormat:#"%#_thumb.jpg", name]];
if (thumbImage) {
ThumbImageView *thumbView = [[ThumbImageView alloc] initWithImage:thumbImage];
[thumbView setDelegate:self];
[thumbView setImageName:name];
CGRect frame = [thumbView frame];
frame.origin.y = THUMB_V_PADDING;
frame.origin.x = xPosition;
[thumbView setFrame:frame];
[thumbView setHome:frame];
[thumbScrollView addSubview:thumbView];
[thumbView release];
xPosition += (frame.size.width + THUMB_H_PADDING);
}
}
[thumbScrollView setContentSize:CGSizeMake(xPosition, scrollViewHeight)];
}
}
#pragma mark Utility methods
- (CGRect)zoomRectForScale:(float)scale withCenter:(CGPoint)center {
CGRect zoomRect;
// the zoom rect is in the content view's coordinates.
// At a zoom scale of 1.0, it would be the size of the imageScrollView's bounds.
// As the zoom scale decreases, so more content is visible, the size of the rect grows.
zoomRect.size.height = [imageScrollView frame].size.height / scale;
zoomRect.size.width = [imageScrollView frame].size.width / scale;
// choose an origin so as to get the right center.
zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0);
zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0);
return zoomRect;
}
#pragma mark -
#pragma mark Rotation support
// Ensure that the view controller supports rotation and that the split view can therefore show in both portrait and landscape.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
#end
If you've set up your UIScrollView in a Nib file, make sure it is resizing properly when rotated (Use the Autosizing controls in the Size Inspector in Interface Builder: Both sets of arrows inside the box should be red)
Then use this to rescale when the iPad changes orientation:
- (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration{
//if your UIImageView is called zoomView use this
CGRect zoomRect=CGRectMake(0,0,zoomView.frame.size.width, zoomView.frame.size.height);
[scrollView zoomToRect:zoomRect animated:YES];}
(Sorry about the bad placement of the {} but it wasn't pasting properly as code for some reason)
Hope this helps!