Background
I've made a custom segue to allow for "overlay cards" to slide down from the top, and then fly away upon dismissal. When writing the code, I realized that all examples used a UIViewControllerTransitioningDelegate to capture when the segue was animate/dismissed. So far so good, but all examples that I found let the presentingController act as the delegate, which to me sounds strange since I need to "distribute" the logics of how my "overlay card" looks like, in two places; the custom segue, and the presenting controller.
Hence, I figured that I could combine the two into one; the custom segue, which would make my storyboard so much neater, since all view-controllers that I want to show as "overlay cards", simply are assigned the "CardSegue" that I've created, instead of having each and every presenting controller implement UIViewControllerTransitioningDelegate.
Problem
From one of the presenting controllers, I get a warning when dismissing the presented "Overlay card";
UIModalPresentationCustom presentation style can only be used with an animator or with unanimated dismissals.
I looked around, and all I could find was another post that seemed promising;
iOS 7.1 UIModalPresentationCustom warning message
But, the problem is that since my custom segue IS the transitionDelegate, trying to keep it as one logical unit, I can't really apply the reasoning of retaining the delegate...? I mean, the segue is running -- hence, the delegate is alive and well. I'm missing something, or there's an obvious reason that I cannot see right now for why the transitionDelegate MUST be separated from the custom segue?
My code
#define POPUP_MARGIN 25
#interface CardSegue () <UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning>
#property (nonatomic, assign, getter = isPresenting) BOOL presenting;
#end
#implementation CardSegue
- (void)perform
{
//Make sure that we are the delegate...
[self.destinationViewController setTransitioningDelegate:self];
[self.destinationViewController setModalPresentationStyle:UIModalPresentationCustom];
//Brute-force show the end-result!
[[self sourceViewController] presentViewController:[self destinationViewController] animated:YES completion:nil];
}
#pragma mark - UIViewControllerTransitioningDelegate Methods
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
presentingController:(UIViewController *)presenting
sourceController:(UIViewController *)source
{
//Configure the animator
self.presenting = YES;
return self;
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
self.presenting = NO;
return self;
}
#pragma mark - UIViewControllerContextTransitioning Methods
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5f;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
// Grab the from and to view controllers from the context
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
if (self.presenting)
{
// Set our ending frame. We'll modify this later if we have to
CGRect frame = fromViewController.view.frame;
CGRect endFrame = CGRectMake(POPUP_MARGIN, POPUP_MARGIN, frame.size.width - 2 * POPUP_MARGIN, frame.size.height - 2 * POPUP_MARGIN);
fromViewController.view.userInteractionEnabled = NO;
toViewController.view.alpha = 0.0f;
toViewController.view.layer.cornerRadius = 15;
//toViewController.view.layer.borderWidth = 1.5f;
toViewController.view.layer.masksToBounds = YES;
UIView *mainView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
mainView.backgroundColor = [[UIColor alloc] initWithRed:0.0 green:0.0 blue:0.0 alpha:0.0];
[transitionContext.containerView addSubview:fromViewController.view];
[transitionContext.containerView addSubview:mainView];
[transitionContext.containerView addSubview:toViewController.view];
CGRect startFrame = endFrame;
startFrame.origin.y -= frame.size.height;
toViewController.view.frame = startFrame;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
mainView.backgroundColor = [[UIColor alloc] initWithRed:0.0 green:0.0 blue:0.0 alpha:0.5];
fromViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeDimmed;
toViewController.view.frame = endFrame;
toViewController.view.alpha = 1.0f;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
else {
// Set our ending frame. We'll modify this later if we have to
CGRect frame = toViewController.view.frame;
CGRect endFrame = CGRectMake(POPUP_MARGIN, POPUP_MARGIN, frame.size.width - 2 * POPUP_MARGIN, frame.size.height - 2 * POPUP_MARGIN);
toViewController.view.userInteractionEnabled = YES;
UIView *mainView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
mainView.backgroundColor = [[UIColor alloc] initWithRed:0.0 green:0.0 blue:0.0 alpha:0.5];
[transitionContext.containerView addSubview:toViewController.view];
[transitionContext.containerView addSubview:mainView];
[transitionContext.containerView addSubview:fromViewController.view];
endFrame.origin.y += frame.size.height;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeAutomatic;
mainView.backgroundColor = [[UIColor alloc] initWithRed:0.0 green:0.0 blue:0.0 alpha:0.0];
fromViewController.view.frame = endFrame;
fromViewController.view.alpha = 0.0f;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
}
#end
Related
All I am trying to do is add a new view to the content view of my NSWindow instance. When I do the following I do not see the new view (which should be black and take up the entire window). What am I doing wrong?
(done in response to a button click)
NSRect frameRect = [self.window frame];
frameRect.origin = NSZeroPoint;
NSView *view = [[NSView alloc] initWithFrame:frameRect];
view.wantsLayer = YES;
view.layer.backgroundColor = [NSColor blackColor].CGColor;
[self.window.contentView addSubview:view];
I've set up a simple project with a push button inside a ViewController and got a warning for accessing self.window. When using self.view.windowthe warning went away and your provided code works as expected.
Updated code
NSRect frameRect = [self.view.window frame];
frameRect.origin = NSZeroPoint;
NSView *view = [[NSView alloc] initWithFrame:frameRect];
view.wantsLayer = YES;
view.layer.backgroundColor = [NSColor blackColor].CGColor;
[self.view.window.contentView addSubview:view];
Update
Assuming that you're using an instance of WindowController where you're adding a button programmatically, your code works as expected.
#implementation WindowController
- (void)windowDidLoad
{
[super windowDidLoad];
CGRect buttonRect = CGRectMake(self.window.frame.size.width / 2 - 50,
self.window.frame.size.height / 2,
100,
50);
NSButton *button = [[NSButton alloc] initWithFrame:NSRectFromCGRect(buttonRect)];
[button setTitle: #"Click me!"];
[button setTarget:self];
[button setAction:#selector(buttonPressed)];
[self.window.contentView addSubview:button];
}
- (void)buttonPressed
{
NSRect frameRect = [self.window frame];
frameRect.origin = NSZeroPoint;
NSView *view = [[NSView alloc] initWithFrame:frameRect];
view.wantsLayer = YES;
view.layer.backgroundColor = [NSColor blackColor].CGColor;
[self.window.contentView addSubview:view];
}
An instance of NSViewController ain't got a property of window - only NSWindowController has one.
Basically, it's a very simple demand, but I've tried several methods and none of them works as expected. The closest functioning snippet is:
#import "ViewController.h"
#implementation ViewController
- (void)dealloc
{
[scrollView release];
scrollView = nil;
[super dealloc];
}
- (id)init
{
if (self = [super init])
{
self.title = #"Pictures";
scrollView = [[UIScrollView alloc] init];
scrollView.delegate = self;
scrollView.frame = CGRectMake(0.0f, 0.0f, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
scrollView.contentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width * 10, scrollView.bounds.size.height);
scrollView.showsVerticalScrollIndicator = NO;
scrollView.showsHorizontalScrollIndicator = NO;
scrollView.pagingEnabled = YES;
scrollView.userInteractionEnabled = YES;
scrollView.backgroundColor = [UIColor blackColor];
self.wantsFullScreenLayout = YES;
self.automaticallyAdjustsScrollViewInsets = NO;
isHidden = NO;
}
return self;
}
- (void)viewDidLoad
{
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tap:)];
[scrollView addGestureRecognizer:tapGesture];
[tapGesture setNumberOfTapsRequired:1];
[tapGesture release];
for (int i = 0; i < 10; i++)
{
UIImage *image = [[UIImage alloc] initWithContentsOfFile:[NSString stringWithFormat:#"/path/to/%d.png", i + 1]];
UIImageView *imageView= [[UIImageView alloc] initWithFrame:CGRectMake([UIScreen mainScreen].bounds.size.width * i, 0.0f, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.image = image;
[scrollView addSubview:imageView];
[image release];
[imageView release];
}
[self.view addSubview:scrollView];
}
- (void)tap:(UITapGestureRecognizer *)gesture
{
[UINavigationBar setAnimationDuration:1.0];
[UINavigationBar beginAnimations:#"HideTopBars" context:nil];
isHidden = !isHidden;
// [self setNeedsStatusBarAppearanceUpdate];
self.navigationController.navigationBar.alpha = isHidden ? 0.0f : 1.0f;
[UINavigationBar commitAnimations];
}
- (void)scrollViewDidScroll:(UIScrollView *)view
{
// further operation
}
- (BOOL)prefersStatusBarHidden
{
return isHidden;
}
#end
Without "[self setNeedsStatusBarAppearanceUpdate]", navigation bar does fade as expected, but texts on status bar remain visible, which I guess is because status bar takes navigation bar as it's background image and status bar itself doesn't fade; With "[self setNeedsStatusBarAppearanceUpdate]", the texts also fade, but navigation bar's animation becomes sliding in/out from the top of the screen along with fade effect. I've also tried to move "[self setNeedsStatusBarAppearanceUpdate]" into prefersStatusBarHidden, but that just made navigation bar visible forever. I believe this is not an odd demand, so I bet there're better and simpler solutions. Any idea?
This code does the following: It takes a 'snapshot' of a NavigationController meant to disappear. Then the snapshot is added to the view of a container view controller, slides away and reveals the new NavigationController. To create the 'Pop-Out-Effect' the midAnFrame makes the view a bit bigger than the actual view. After that postAnFrame provides the sliding out of the container view's frame. Works great, the only problem is that the subviews of animationContainer don't resize like their superView during the animation.
- (void)bringUpSettingsNavigationController {
CGFloat gradientWidth = 20;
CGFloat frameIncrease = 10;
CGRect preAnFrame = CGRectMake(- gradientWidth, 0, self.view.frame.size.width + gradientWidth, self.view.frame.size.height);
CGRect midAnFrame = CGRectMake(-(frameIncrease + gradientWidth), - frameIncrease, preAnFrame.size.width + (2*frameIncrease), preAnFrame.size.height + (2*frameIncrease));
CGRect postAnFrame = CGRectMake(midAnFrame.size.width, midAnFrame.origin.y, midAnFrame.size.width, midAnFrame.size.height);
UIView *animationContainer = [[UIView alloc]initWithFrame:preAnFrame];
animationContainer.backgroundColor = [UIColor clearColor];
animationContainer.autoresizesSubviews = YES;
self.topImageView = [[UIImageView alloc]initWithImage:[self topImageFromView:self.view]];
self.topImageView.frame = CGRectMake(gradientWidth, 0, preAnFrame.size.width - gradientWidth, preAnFrame.size.height);
[animationContainer addSubview:self.topImageView];
self.topImageView.clipsToBounds = YES;
UIView *gradientView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, gradientWidth, preAnFrame.size.height)];
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = gradientView.bounds;
gradientLayer.colors = [NSArray arrayWithObjects:(id)[[UIColor clearColor] CGColor], (id)[[UIColor colorWithWhite:0 alpha:.5] CGColor], nil];
gradientLayer.startPoint = CGPointMake(0, 0.5);
gradientLayer.endPoint = CGPointMake(1.0, 0.5);
[gradientView.layer insertSublayer:gradientLayer atIndex:0];
gradientView.backgroundColor = [UIColor clearColor];
[animationContainer addSubview:gradientView];
gradientView.clipsToBounds = YES;
[self.view addSubview:animationContainer];
BYSettingsNavigationController *settingsNavigationController = [[BYSettingsNavigationController alloc]initWithRootViewController:[[BYSettingsViewController alloc]init]];
[self addChildViewController:settingsNavigationController];
settingsNavigationController.view.frame = self.view.bounds;
[self.view insertSubview:settingsNavigationController.view belowSubview:animationContainer];
[settingsNavigationController didMoveToParentViewController:self];
[UIView animateWithDuration:0.2 animations:^{
animationContainer.frame = midAnFrame;
} completion:^(BOOL finished) {
[UIView animateWithDuration:.5 animations:^{
animationContainer.frame = postAnFrame;
} completion:^(BOOL finished) {
[self.topImageView removeFromSuperview];
[gradientView removeFromSuperview];
self.topImageView = nil;
}];
}];
[self.currentNavigationController willMoveToParentViewController:nil];
[self.currentNavigationController.view removeFromSuperview];
[self.currentNavigationController removeFromParentViewController];
}
Thanks in advance for your ideas and have a great 2013!
Dario
What your doing is not zooming in , your just changing the frame size.
To zoom in you need to change the transformation of the view.
What you need to do is something along this line:
view.transform = CGAffineTransformMakeScale (2.0 , 2.0);
for example to zoom X2
I have an UIImageView in a UITableviewCell. When it is tapped, the UIImageView should animated to be displayed fullscreen. When the image is tapped when it is fullscreen it should shrink back to the original position.
How can this be achieved?
Add a gesture recognizer to the view controller.
Add the gesture Recognizer to your header file
#interface viewController : UIViewController <UIGestureRecognizerDelegate>{
UITapGestureRecognizer *tap;
BOOL isFullScreen;
CGRect prevFrame;
}
In your viewDidLoad add this:
isFullScreen = false;
tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imgToFullScreen)];
tap.delegate = self;
Add the following delegatemethod:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
{
BOOL shouldReceiveTouch = YES;
if (gestureRecognizer == tap) {
shouldReceiveTouch = (touch.view == yourImageView);
}
return shouldReceiveTouch;
}
Now you just need to implement your imgToFullScreen method.
Make sure you work with the isFullScreen Bool (fullscreen if it is false and back to old size if it's true)
The imgToFullScreen method depends on how you want to make the image become fullscreen.
One way would be:
(this is untested but should work)
-(void)imgToFullScreen{
if (!isFullScreen) {
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
//save previous frame
prevFrame = yourImageView.frame;
[yourImageView setFrame:[[UIScreen mainScreen] bounds]];
}completion:^(BOOL finished){
isFullScreen = true;
}];
return;
} else {
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
[yourImageView setFrame:prevFrame];
}completion:^(BOOL finished){
isFullScreen = false;
}];
return;
}
}
The code from #AzzUrr1, small error corrections (brackets) and tapper implemented slightly different.
Worked for me. Now it would be great to have this implemented with a scrollView, that the user can zoom in/out if the picture is bigger.. Any suggestion?
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UIGestureRecognizerDelegate>{
UITapGestureRecognizer *tap;
BOOL isFullScreen;
CGRect prevFrame;
}
#property (nonatomic, strong) UIImageView *imageView;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
isFullScreen = FALSE;
tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imgToFullScreen)];
tap.delegate = self;
self.view.backgroundColor = [UIColor purpleColor];
_imageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 10, 300, 200)];
_imageView.contentMode = UIViewContentModeScaleAspectFill;
[_imageView setClipsToBounds:YES];
_imageView.userInteractionEnabled = YES;
_imageView.image = [UIImage imageNamed:#"Muppetshow-2.png"];
UITapGestureRecognizer *tapper = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imgToFullScreen:)];
tapper.numberOfTapsRequired = 1;
[_imageView addGestureRecognizer:tapper];
[self.view addSubview:_imageView];
}
-(void)imgToFullScreen:(UITapGestureRecognizer*)sender {
if (!isFullScreen) {
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
//save previous frame
prevFrame = _imageView.frame;
[_imageView setFrame:[[UIScreen mainScreen] bounds]];
}completion:^(BOOL finished){
isFullScreen = TRUE;
}];
return;
}
else{
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
[_imageView setFrame:prevFrame];
}completion:^(BOOL finished){
isFullScreen = FALSE;
}];
return;
}
}
I ended up using MHFacebookImageViewer. Integration is easy, no subclassing UIImageView, and it also has image zooming and flick dismiss.
Although it requires AFNetworking (for loading larger image from URL), you can comment out some code (about 10 lines) to remove this dependency. I can post my AFNetworking-free version if someone needs it. Let me know :)
One possible implementation would be to use a modal view controller with UIModalPresentationFullScreen presentation style.
Just finished a version in swift, just download and add into your project:
GSSimpleImageView.swift
And usage:
let imageView = GSSimpleImageView(frame: CGRectMake(20, 100, 200, 200))
imageView.image = UIImage(named: "test2.png")
self.view.addSubview(imageView)
- (void)viewDidLoad
{
adView = [[ADBannerView alloc] initWithFrame:CGRectZero];
adView.requiredContentSizeIdentifiers = [NSSet setWithObject:ADBannerContentSizeIdentifierPortrait];
adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifierPortrait;
CGRect adFrame = adView.frame;
adFrame.origin.y = self.view.frame.size.height-adView.frame.size.height;
adView.frame = adFrame;
[self.view addSubview:adView];
}
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
if (!self.bannerIsVisible)
{
self.bannerIsVisible = YES;
}
}
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
if (self.bannerIsVisible)
{
self.bannerIsVisible = NO;
}
There are a four things. First, you should be positioning the banner off screen in your viewDidLoad method because it will just show an empty frame when you first launch and will more than likely get rejected because of it.
Secondly, you are setting up your banner view incorrectly. I think the frame is still CGZero. Thirdly you are not setting the bannerView's delegate. Try the following:
-(void)viewDidLoad{
CGRect frame=CGRectZero;
frame.size = [ADBannerView sizeFromBannerContentSizeIdentifier:ADBannerContentSizeIdentifierPortrait];
// Place frame at the bottom edge of the screen out of sight
frame.origin = CGPointMake(0.0, CGRectGetMaxY(self.view.bounds));
// Now to create and configure the banner view
ADBannerView *adView = [[ADBannerView alloc] initWithFrame:frame];
adView.requiredContentSizeIdentifiers = [NSSet setWithObject:ADBannerContentSizeIdentifierPortrait];
// Set the delegate to self, so that we are notified of ad responses
adView.delegate = self;
[self.view addSubview: adView];
}
Fourth, in your bannerViewDidLoadAd: method you are not animating the banner ad into place. Try this:
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
// Get a brand new frame
CGRect newFrame=CGRectZero;
CGPoint frameOrigin=CGPointZero;
// Set the origin
frameOrigin=CGPointMake(0.0, CGRectGetMaxY(self.view.bounds));
newFrame.origin=frameOrigin;
// Set the size
newFrame.size=[ADBannerView sizeFromBannerContentSizeIdentifier:ADBannerContentSizeIdentifierPortrait];
CGFloat bannerHeight = newFrame.size.height;
CGFloat bannerOffset=0.0;
// Determine where the new frame should be
if (!self.bannerIsVisible)
{
// It should be visible, raise it up
bannerOffset=-bannerHeight;
}
CGRect offSetRect=CGRectOffset(newFrame,0.0f,bannerOffset);
[UIView animateWithDuration:0.2
animations:^{banner.view.frame=offSetRect}
completion:^(BOOL finished){
if (bannerOffSet<0){
self.bannerIsVisible=YES;
}else{
self.bannerIsVisible=NO;
}
}
];
}
of course if the banner is supposed to be positioned at the top of the screen, you can probably figure out how things need to be modified, but this gets you in going in the right direction.
Good luck
Not sure by reading your question, but do be aware that Apple simulates iAds not being available. Sometimes you need to try multiple times before the sample ad comes through.