I would like to replicate the exact same didSelect animation / segue when you tap a photo in the iPhone's photo app (or in almost every so other app) where the photo enlarges from the cell itself into a modal view controller, and minimizes to wherever it belongs to in the grid when dismissed.
I tried googling but couldn't find any articles about this.
There are many public repos on git that could probably do what you want. Some stuff I've found:
https://github.com/mariohahn/MHVideoPhotoGallery
https://github.com/mwaterfall/MWPhotoBrowser
Those may be overly complicated. Another option is creating a UIImageView at the same place as the cell and then animating it to fill the screen. This code assumes the collectionView has an origin at (0,0), if not then simply add the collectionView's offset when calculating the initial frame.
collectionView.scrollEnabled = false; // disable scrolling so view won't move
CGPoint innerOffset = collectionView.contentOffset; // offset of content view due to scrolling
UICollectionViewLayoutAttributes *attributes = [collectionView layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0] ];
CGRect cellRect = attributes.frame; // frame of cell in contentView
UIImageView *v = [[UIImageView alloc] initWithFrame:CGRectMake(cellRect.origin.x - innerOffset.x, cellRect.origin.y - innerOffset.y, cellRect.size.width, cellRect.size.height)];
[self.view addSubview:v]; // or add to whatever view you want
v.image = image; // set your image
v.contentMode = UIViewContentModeScaleAspectFit; // don't get stupid scaling
// animate
[UIView animateWithDuration:0.5 animations:^{
[v setFrame:[[UIScreen mainScreen] bounds]]; // assume filling the whole screen
}];
It's not the nice popping animation but it should still look ok.
Related
I would like to emulate the swipe to left to show next picture seen in the photos app, among other places so that the new picture slides in from right to left using animation.
However, in my case, the view has more than one photo. It has a caption as well.
I am able to do this without animation by just updating the photo and text upon swipe. This just changes the photo and caption on screen, however, without the right to left animation technique.
Is there a way to show the old view moving to the left while the new updated view moves in from the right.
The following moves the view to the left but renders the screen black as nothing is put in its place.
//In handleSwipe called by gesture recognizer
CGRect topFrame = self.view.frame;
topFrame.origin.x = -320;
[UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
self.view.frame = topFrame; _myImage=newImage;
_mycaption=newcaption;} completion:^(BOOL finished){ }];
Thanks for any suggestions.
Edit:
The view in question is a detail view that you get to from a tableview through a segue. It currently already uses a scrollview for vertical scrolling as the content can exceed a single screen in the vertical dimension.
I have found a way to get the old photo and caption to move left and the new to come in from right using a completion block, however, it is less than satisfactory, because there is a gap between the two actions when the screen is black. This seems endemic to completion approach because new image does not start to move until old image has completed moving.
- (IBAction)swipedLeft:(id)sender
{
CGRect initialViewFrame = self.view.frame;
CGRect movedToLeftViewFrame = CGRectMake(initialViewFrame.origin.x - 375, initialViewFrame.origin.y, initialViewFrame.size.width, initialViewFrame.size.height);
CGRect movedToRightViewFrame = CGRectMake(initialViewFrame.origin.x + 375, initialViewFrame.origin.y, initialViewFrame.size.width, initialViewFrame.size.height);
[UIView animateWithDuration:0.1
animations:^{self.view.frame = movedToLeftViewFrame;
//above moves old image out of view.
}
completion:^(BOOL finished)
{
self.view.frame = movedToRightViewFrame;
[self loadNextItem];//changes pic and text.
[UIView animateWithDuration:0.1
animations:^{self.view.frame = initialViewFrame;
//moves new one in
}
completion:nil];
}];
}
I suggest using a UIPageViewController, and manage each image/set of images as a separate view controller.
A page view controller supports either a page curl style transition or a slide transition. You'd want the slide transition.
Three ways to build this using existing components:
UIPageViewController (see DuncanC's answer).
UICollectionView with pagingEnabled set to true. Use a full-screen-sized cell and a UICollectionViewFlowLayout with scrollDirection set to horizontal.
UICollectionView as above, but instead of setting pagingEnabled to true, implement scrollViewWillEndDragging:withVelocity:targetContentOffset: in your delegate. This allows you to have a gutter between each cell while still having each cell fill the screen. See this answer.
It's ScrollView+PageControl
I hope it can help you.
- (void)viewDidLoad {
[super viewDidLoad];
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.delegate =self;
self.scrollView.translatesAutoresizingMaskIntoConstraints =NO;
self.scrollView.pagingEnabled =YES;
self.scrollView.contentSize =CGSizeMake(ViewWidth*VIewCount, ViewHeight);
[self.view addSubview:self.scrollView];
self.pageControl = [[UIPageControl alloc] init];
self. pageControl.translatesAutoresizingMaskIntoConstraints =NO;
[self.view addSubview:self.pageControl];
self. pageControl.numberOfPages = VIewCount;
self.pageControl.currentPage = 0;
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.scrollView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0f
constant:0.0f]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"[self.scrollView(width)]"
options:0
metrics:#{#"width":#(ViewWidth)}
views:NSDictionaryOfVariableBindings(self.scrollView)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-50-[self.scrollView(height)]"
options:0
metrics:#{#"height":#(HEIGHT_VIEW)}
views:NSDictionaryOfVariableBindings(self.scrollView)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[_scrollView]-5-[pageControl(15)]"
options:NSLayoutFormatAlignAllCenterX
metrics:nil
views:NSDictionaryOfVariableBindings(self.scrollView, self.pageControl)]];
//[imgArr addObject:[UIImage imageNamed:...]];
for (NSInteger index = 0; index<ViewCount; index++) {
UIImageView *addSubview = [[UIImageView alloc] initWithFrame:CGRectMake(index*ViewWidth, 0, ViewWidth, ViewHeight)];
// addSubView.image = [imgArr objectAtIndex:index];
[self.scrollView addSubview:addSubview];
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSInteger currentOffset = scrollView.contentOffset.x;
NSInteger index = currentOffset / ViewWidth;
if (currentOffset % ViewWidth == 0) {
pageControl.currentPage = index;
}
}
so I have a very simple button that when clicked goes to fullscreen and when clicked again goes back to the same position it was initially in. For some reason it works perfectly without the animation. When I uncomment the animation part when I initially click the button it does nothing, the second time I click it slightly enlarges. The third time I click it animates slowly but back to it's smaller original size... Why is it animating the opposite way?
- (IBAction)viewImage1:(id)sender {
UIButton *btn = (UIButton*) sender;
if (btn.tag == 0)
{
CGRect r = [[UIScreen mainScreen] bounds];
/*[UIView animateWithDuration:0.5f delay:0.0f options:0 animations:^{*/
[sender setFrame: r];
/*}completion:nil];*/
btn.tag = 1;
}
else
{
btn.tag = 0;
[sender setFrame:CGRectMake(0,0,370,200)];
}
}
There are two solutions to your problem either of which will work:
Disable Autolayout. (discouraged)
You can do that in Interface Builder by opening the File Inspector
in the right pane and unchecking the respective check box.
However, if you want to use Autolayout for constraining other UI elements in your view (which is quite a good idea in most cases) this approach won't work which is why I would recommend the second solution:
Keep Autolayout enabled, create an outlet for your button in your view controller and set
self.myButton.translatesAutoresizingMaskIntoConstraints = YES;
in your view controller's viewDidLoad method.
You could also add layout constraints to your button and animate those. (This excellent Stackoverflow post explains how it's done.)
The reason for this tricky behavior is that once you enable Autolayout a view's frame is no longer relevant to the actual layout that appears on screen, only the view's layout constraints matter. Setting its translatesAutoresizingMaskIntoConstraints property to YES causes the system to automatically create layout constraints for your view that will "emulate" the frame you set, in a manner of speaking.
It is easier to do this with auto layout and constraints. Create an IBOutlet for the height constraint of your button call it something like btnHeight. Do the same for the width constraint call it something like btnWidth. Then create an IBAction like so:
- (IBAction)buttonPress:(UIButton *)sender {
UIButton *btn = (UIButton *) sender;
CGRect r = [[UIScreen mainScreen] bounds];
if (CGRectEqualToRect(btn.frame, CGRectMake(0.0, 0.0, 370, 200))) {
[UIView animateWithDuration:0.5 delay:0.0 options:0 animations:^{
self.btnHeight.constant = r.size.height;
self.btnWidth.constant = r.size.width;
[self.view layoutIfNeeded];
} completion:^(BOOL finished){
}];
}else{
[UIView animateWithDuration:0.5 delay:0.0 options:0 animations:^{
self.btnHeight.constant = 200.0;
self.btnWidth.constant = 370.0;
[self.view layoutIfNeeded];
} completion:^(BOOL finished){
}];
}
}
In my experience animating the frame of a UIButton does not work well, a, the only method, I'm aware of, is to use CGAffineTransformScale which will rasterize the title of the button and scale it as well.
I'm trying to have my collectionview cell/uiimageview go full screen when the image is tapped. I'm using animation and doing this from didSelectItemAtIndexPath and everything works and looks great, except that the imageview does not change size at all, and obviously is not going full screen like I want. I can see the animation working, however it all happens within the cell's preexisting size, and therefore, the image gets cropped. I would like it go full screen when tapped, similarly to the way it works in FB when tapping a photo. Appreciate any help! Here's my code...
#interface BaseViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UITextFieldDelegate> {
CGRect prevFrame;
}
#property (nonatomic) BOOL isFullScreen;
- (void)updateUI;
#end
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
ImageViewCell *selectedCell = (ImageViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
NSLog(#"didSelectItemAtIndexPath");
if (!self.isFullScreen) {
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
NSLog(#"starting animiation!");
prevFrame = selectedCell.imageView.frame;
selectedCell.imageView.center = self.view.center;
selectedCell.imageView.backgroundColor = [UIColor blackColor];
//[selectedCell.imageView setFrame:[[UIScreen mainScreen] bounds]];
[selectedCell.imageView setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
}completion:^(BOOL finished){
self.isFullScreen = YES;
}];
return;
} else {
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
[selectedCell.imageView setFrame:prevFrame];
selectedCell.imageView.backgroundColor = [UIColor colorWithRed:0.62 green:0.651 blue:0.686 alpha:1];
}completion:^(BOOL finished){
self.isFullScreen = NO;
}];
return;
}
}
Your view cell is embedded in a view hierarchy, and most views clip their sub-views. To get a "full-screen" effect you're going to need to either (a) set parent views of your image view (the cell, the collectionView, anything that might be embedded in, etc) not not clip subviews (see: "clips to bounds") or (b) put your image in a view that is not embedded in those containers.
I don't recommend (a). Trying to set containers to allow child-views (your UIImageView) that are bigger than the container is kind of an anti-pattern that breaks the idea that containers manage and contain the display of their children.
For (b), what you would want to do is:
on Tap:
imageView = << create a new UIImageView >>
[self.view addSubview:imageView]
set imageView.image = collectionCell.imageView.image
set imageView.frame = << translated rect of the collectionCell.imageView.frame >>
... animate blowing your new imageView up to full-screen...
So you're basically treating the "tap on cell" action as a command to "create a new UIImageView to show the image, and animate that to full-screen."
Since the new image view is a direct child of the VC's root view, it can be sized full-screen without getting clipped by a parent view.
I have a UITableView cell that has a custom label inside to handle the variable height. There is also an UIImage on the right and left.
When the table is toggled into edit mode, I want to inform and change each cell in the table, to format properly for being shifted to the right. And, when the user presses the small -, I want to further optimize to make room for the delete button.
Looking for a pattern that works for the above, assuming there is custom content in the cell that I have control over.
Thanks in advance!
Normaly all you need to do is set the springs and struts correctly and your content will slide correctly. If you create your sub-views in code then you need to make sure you call addSubview on the cell.contentView and not the cell.
To hide and / or resize the sub-views you need to override willTransitionToState:
- (void)willTransitionToState:(UITableViewCellStateMask)state
{
UIView *imageView = self.rightImageView;
UIView *labelView = self.centerTextLabel;
CGRect labelFrame = labelView.frame;
if (state & UITableViewCellStateShowingDeleteConfirmationMask) {
labelFrame.size.width += 52;
// Animating the fade while the image is sliding to the left
// is more jarring then just making it go away immediately
imageView.alpha = 0.0;
[UIView animateWithDuration:0.3 animations:^{
labelView.frame = labelFrame;
}];
} else if (!self.rightImageView.alpha) {
labelFrame.size.width -= 52;
[UIView animateWithDuration:0.3 animations:^{
imageView.alpha = 1.0;
labelView.frame = labelFrame;
}];
}
[super willTransitionToState:state];
}
I created a quick sample app up on GitHub that demos an iOS 4.3 app using a nib or code just uncomment //#define USE_NIB_TABLEVIEWCELL to use the nib
https://github.com/GayleDDS/TestTableCell.git
I personally prefer to create the table view cell in a nib file and only after profiling the App, replace the nib with code.
In my UICollectionView, one of the cells contains an animation will go out of the cell's bounds, and now it is covered by other cells nearby. Is it possible to make this special cell on top of others, so it can present animation perfectly?
Thanks.
I had this same need
Specifically, I want to present a modal view controller based on the cell within the collection view that was selected. You can see this sort of behavior in iBooks when you are looking at the library. Select a "book" from the collection, and it "pops out" and opens to the book.
Simply scaling the cell "as is" may have it overlaid by other cells, which is not desirable.
To get this to work, you simply remember the cell's superview (it may not be the UICollectionView directly!) and its original center point. Then you remove it from its superview, add it to your main view (optionally bringing it to the front), perform your animation. In the completion block, reset your transform to Identity (no transform), remove it from your main view, and add it back to its original parent and center point.
I have example code below, but there are a few caveats:
I use IVARS for animation state information because I use the information in multiple methods in my actual implementation
I'm also using QuartzCore 3D transformations due to additional transformations my code does that is outside the scope of this answer
I added an "animating" property to my cell to have it alter its appearance during the animation (so it will scale nicer)
Finally, I switched my code to do a standard modal presentation at the end in this example, rather than calling a non-standard presentation controller which I did in my actual app for the actual launching of the next VC in the scene
There are probably other tweaks you will need to do as well, but the basic technique I outlined above will definitely work.
// Assume: UIView *cell == cell we are animating
// Assume: UIViewController *vc == View controlelr we will launch
// Assume: IVAR CGPoint _animationCellOrigCenter
// Assume: IVAR UIView *_animationCellParent
// Assume: IVAR UIView *_animationCell;
// Save "before" state before animating:
_animationCellOrigCenter = cell.center;
_animationCellParent = cell.superview;
_animationCell = cell;
// Where is the cell currently in the main view?
CGPoint cellViewLoc = [self.view convertPoint:_animationCellOrigCenter fromView:_animationCellParent];
// OK. Hoist the cell into the main view:
[cell removeFromSuperview];
cell.center = cellViewLoc;
[self.view addSubview:cell];
[self.view bringSubviewToFront:cell];
CGSize cellSize = cell.bounds.size;
CGSize newSize = CGSizeMake(600,600); // YMMV
[cell.superview bringSubviewToFront:cell];
// Get ready to do some animating:
CGSize viewSize = collectionView.bounds.size;
CGPoint destination = CGPointMake(viewSize.width / 2, viewSize.height / 2);
CGFloat scaleX = newSize.width / cellSize.width;
CGFloat scaleY = newSize.height / cellSize.height;
cell.animating = YES;
[UIView animateWithDuration:.2
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction
animations:^{
cell.layer.transform = CATransform3DMakeScale(scaleX, scaleY, 1);
cell.center = destination;
}
completion:^(BOOL finished) {
[self presentViewController:vc
animated:YES
completion:^{
// Put everything back the way we found it!
[_animationCell removeFromSuperview];
[_animationCellParent addSubview:_animationCell];
_animationCell.center = _animationCellOrigCenter;
_animationCell.layer.transform = CATransform3DIdentity;
_animationCell.animating = NO;
}
];
}
];
This is fairly straightforward. Just add the cell to the collectionview's scrollview before you expand it on top of the others, and then remove it when you're no longer wanting it on top. Below I've done this and then added an animation on each cell itself to have it expand on top the others. I've left that animation out for simplicity.
override func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
let cell = collectionView.cellForItemAtIndexPath(indexPath)
self.collectionView?.addSubview(cell!)
return true
}
override func collectionView(collectionView: UICollectionView, shouldDeselectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
let cell = collectionView.cellForItemAtIndexPath(indexPath)
if (cell?.superview != nil) {
cell?.removeFromSuperview()
}
return true
}