I would like to present a modal View Controller with a custom Segue including UIKit Dynamics.
Copied the following code by rdelmar in this post https://stackoverflow.com/a/23039173/1016102 and tried to modify it for what i want.
-(id)initWithIdentifier:(NSString *)identifier source:(UIViewController *)source destination:(UIViewController *)destination {
if (self = [super initWithIdentifier:identifier source:source destination:destination]) {
UIViewController *src = self.sourceViewController;
UIViewController *dest = self.destinationViewController;
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:src.view];
[src addChildViewController:dest];
[dest didMoveToParentViewController:src];
dest.view.frame = CGRectMake(0, 300, src.view.bounds.size.width, src.view.bounds.size.height);
[src.view addSubview:dest.view];
}
return self;
}
-(void)perform {
UIGravityBehavior* gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[[self.destinationViewController view]]];
CGVector vector = CGVectorMake(0, -1);
[gravityBehavior setGravityDirection:vector];
UICollisionBehavior *collide = [[UICollisionBehavior alloc] initWithItems:#[[self.destinationViewController view]]];
CGPoint left = CGPointMake(self.animator.referenceView.bounds.origin.x, self.animator.referenceView.bounds.origin.y + self.animator.referenceView.bounds.size.height);
CGPoint right = CGPointMake(self.animator.referenceView.bounds.origin.x + self.animator.referenceView.bounds.size.width, self.animator.referenceView.bounds.origin.y + self.animator.referenceView.bounds.size.height);
[collide addBoundaryWithIdentifier:#"top" fromPoint:left toPoint:right];
[collide setCollisionDelegate:self.sourceViewController];
[self.animator addBehavior:gravityBehavior];
[self.animator addBehavior:collide];
}
Sadly my destination Controller appears on the wrong position and is disapperaing directly after the collision.
I would like to achive some result as in the following sketch.
Actually my UI is acting like this: I tap the "+" button which fires my custom-segue and the the black box pops directly under the orange NavigationBar. it doesn't pop from the bottom border of the orange box like in the sketch. it just appears and disappers with popping over the top.
What should i edit to achive this?
Related
Whenever we show popover the neighbhouring areas show different kind of gray color and the active tab bar icon color changes from blue to gray till pop over is there.
when the popover is dismissed , the grey shade gets removed
I would like to remove the color when the the popover is visible
I googled but I couldn't find anyway seems like this default behaviour.
any help to help me fix the problem is appreciated
Thanks
For achieve this, you can create own custom popover background of UIPopoverBackgroundView.
you can find the below code for creating custom CustomPopoverBgView.
CustomPopoverBgView.h
#import <UIKit/UIKit.h>
#interface CustomPopoverBgView : UIPopoverBackgroundView
{
UIImageView *_borderImageView;
UIImageView *_arrowView;
CGFloat _arrowOffset;
UIPopoverArrowDirection _arrowDirection;
}
#end
CustomPopoverBgView.m
#import "CustomPopoverBgView.h"
#define CONTENT_INSET 10.0
#define CAP_INSET 25.0
#define ARROW_BASE 25.0
#define ARROW_HEIGHT 25.0
-(instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if(self){
self.layer.shadowColor = [[UIColor clearColor] CGColor];
_borderImageView = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:#"popover-bg.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(CAP_INSET,CAP_INSET,CAP_INSET,CAP_INSET)]];
_arrowView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"arrow.png"]];
[self addSubview:_borderImageView];
[self addSubview:_arrowView];
}
return self;
}
+(UIEdgeInsets)contentViewInsets{
return UIEdgeInsetsMake(CONTENT_INSET, CONTENT_INSET, CONTENT_INSET, CONTENT_INSET);
}
+(CGFloat)arrowHeight{
return ARROW_HEIGHT;
}
+(CGFloat)arrowBase{
return ARROW_BASE;
}
- (CGFloat) arrowOffset {
return _arrowOffset;
}
- (void) setArrowOffset:(CGFloat)arrowOffset {
_arrowOffset = arrowOffset;
}
- (void)setArrowDirection:(UIPopoverArrowDirection)arrowDirection {
_arrowDirection = arrowDirection;
}
- (UIPopoverArrowDirection)arrowDirection {
return _arrowDirection;
}
#end
Calling of CustomPopoverBgView in UIPopoverController
UIButton *btn = (UIButton *)sender;
ViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"dddddd"];
controller.view.backgroundColor = [UIColor redColor];
UIPopoverController *popoverController = [[UIPopoverController alloc] initWithContentViewController:controller] ;
popoverController.popoverBackgroundViewClass = [CustomPopoverBgView class];
[popoverController presentPopoverFromRect:btn.frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:true];
Hope this will help you.
Create your own custom popover view and add it as a subview on top of your main view instead of the default one Apple provided.
I'd like to have an underline that indicates which item was selected. It slides to any other items whenever the item was tapped. Therefore, I added a subview to the custom UITabBarController and set the animation. Then I use hidesBottomBarWhenPushed to hide the tab bar when pushed. However, the underline seems not combined with the custom UITabBarController.
How to handle the subview so it is always on top even when using the back gesture? This Flipboard app capture is what I want to do.
Edit:
CustomTabBarController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// create underline view
CGRect tabBarFrame = self.tabBar.frame;
CGFloat itemWidth = (CGFloat)CGRectGetWidth(tabBarFrame) / MIN(5, self.tabBar.items.count);
CGFloat originX = (CGFloat)itemWidth * self.selectedIndex;
CGRect underlineFrame = CGRectMake(originX, CGRectGetMaxY(tabBarFrame) - 3.0f, itemWidth, 3.0f);
self.underlineView = [[UIView alloc] initWithFrame:underlineFrame];
self.underlineView.backgroundColor = [UIColor redColor];
[self.view addSubview:self.underlineView];
}
#pragma mark - UITabBarDelegate
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
NSUInteger itemIndex = [tabBar.items indexOfObject:item];
CGRect underlineFrame = self.underlineView.frame;
CGFloat originX = (CGFloat)CGRectGetWidth(self.underlineView.frame) * itemIndex;
// underline shifting animation
[UIView animateWithDuration:0.25
animations:^{
self.underlineView.frame = CGRectMake(originX, underlineFrame.origin.y, CGRectGetWidth(underlineFrame), CGRectGetHeight(underlineFrame));
}];
}
CustomTableViewController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UIViewController *detailViewController = segue.destinationViewController;
detailViewController.hidesBottomBarWhenPushed = YES;
}
hidesBottomBarWhenPushed hides the tab bar but its subview (the underline view).
If I hide it by myself and show it in viewWillAppear, the underline view does not look like on top of the tab bar.
I finally found a workaround. To override the method hidesBottomBarWhenPushed, then you can add an alternative view for tab bar's subviews.
sourceViewController.m
- (BOOL)hidesBottomBarWhenPushed
{
[super hidesBottomBarWhenPushed];
CustomTabBarController *tabBarController = (CustomTabBarController *)self.tabBarController;
if (tabBarController.underlineView.isHidden) {
CGRect tabBarBounds = tabBarController.tabBar.bounds;
CGFloat underlineHeight = CGRectGetHeight(tabBarController.underlineView.frame);
CGFloat itemWidth = (CGFloat)CGRectGetWidth(tabBarBounds) / MIN(5, tabBarController.tabBar.items.count);
CGFloat originX = (CGFloat)itemWidth * tabBarController.selectedIndex;
UIView *alternativeView = [[UIView alloc] initWithFrame:CGRectMake(originX,
CGRectGetMaxY(tabBarBounds) - underlineHeight,
itemWidth,
underlineHeight)];
alternativeView.tag = tabBarController.underlineViewTag;
alternativeView.backgroundColor = tabBarController.underlineView.backgroundColor;
[tabBarController.tabBar addSubview:alternativeView];
}
return NO;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CustomTabBarController *tabBarController = (CustomTabBarController *)self.tabBarController;
if (tabBarController.underlineView.isHidden) {
tabBarController.underlineView.hidden = NO;
NSInteger underlineViewTag = tabBarController.underlineViewTag;
UIView *alternativeView = [tabBarController.tabBar viewWithTag:underlineViewTag];
[alternativeView removeFromSuperview];
}
}
Don't forget the case that interactivePopGesture failure to popover view controller, the alternative view still be added to tab bar. So remove it at destination view controller if needed.
destinationViewController.m
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CustomTabBarController *tabBarController = (CustomTabBarController *)self.tabBarController;
NSInteger underlineViewTag = tabBarController.underlineViewTag;
UIView *alternativeView = [tabBarController.tabBar viewWithTag:underlineViewTag];
if (alternativeView) [alternativeView removeFromSuperview];
}
I'm trying to make a view fall as a custom segue transition, but when the perform method is called in the UIStoryboardSegue implementation, it does not fall. I have tried moving the view to be dropped into the source's view to see if it does anything, but it doesn't.
-(void)perform {
UIViewController *src = (UIViewController *)self.sourceViewController;
UIViewController *dest = (UIViewController *)self.destinationViewController;
UIView *viewdrop = [dest.view snapshotViewAfterScreenUpdates:YES];
viewdrop.frame = CGRectMake(0, -src.view.frame.size.height, dest.view.frame.size.width, dest.view.frame.size.height);
[src.view addSubview:viewdrop];
animator = [[UIDynamicAnimator alloc] initWithReferenceView:src.view];
UIGravityBehavior* gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[viewdrop]];
[animator addBehavior:gravityBehavior];
}
The reason it doesn't drop is because the gravity behavior takes time, but the segue itself is deallocated as soon as the perform method finishes. So, you need a way to keep the segue alive at least until the movement is complete. One way to do this is to make a strong property for the segue in the source view controller, and set its value in prepareForSegue,
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
self.dropSegue = segue;
}
I made a modified version of your segue that also adds a collision behavior, and sets the source view controller as the delegate of the collision behavior, so I can use the delegate method, collisionBehavior:endedContactForItem:withBoundaryIdentifier:, to set the dropSegue property to nil (after a slight delay) which causes the segue to be deallocated,
-(void)collisionBehavior:(UICollisionBehavior *)behavior endedContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier {
NSLog(#"collision ended with %#", identifier);
[self performSelector:#selector(setDropSegue:) withObject:nil afterDelay:1];
}
Here is my version of the gravity drop segue,
#interface RDSegue ()
#property (strong, nonatomic) UIDynamicAnimator *animator;
#end
#implementation RDSegue
-(id)initWithIdentifier:(NSString *)identifier source:(UIViewController *)source destination:(UIViewController *)destination {
if (self = [super initWithIdentifier:identifier source:source destination:destination]) {
UIViewController *src = self.sourceViewController;
UIViewController *dest = self.destinationViewController;
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:src.view];
[src addChildViewController:dest];
[dest didMoveToParentViewController:src];
dest.view.frame = CGRectMake(0, -src.view.bounds.size.height, src.view.bounds.size.width, src.view.bounds.size.height);
[src.view addSubview:dest.view];
}
return self;
}
-(void)perform {
UIGravityBehavior* gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[[self.destinationViewController view]]];
UICollisionBehavior *collide = [[UICollisionBehavior alloc] initWithItems:#[[self.destinationViewController view]]];
CGPoint left = CGPointMake(self.animator.referenceView.bounds.origin.x, self.animator.referenceView.bounds.origin.y + self.animator.referenceView.bounds.size.height);
CGPoint right = CGPointMake(self.animator.referenceView.bounds.origin.x + self.animator.referenceView.bounds.size.width, self.animator.referenceView.bounds.origin.y + self.animator.referenceView.bounds.size.height);
[collide addBoundaryWithIdentifier:#"bottom" fromPoint:left toPoint:right];
[collide setCollisionDelegate:self.sourceViewController];
[self.animator addBehavior:gravityBehavior];
[self.animator addBehavior:collide];
}
-(void)dealloc {
NSLog(#"In dealloc");
}
I read two questions here about that, both with the same answer. The suggested solution was to create the UIScrollView object, and then add it to the parent view of the SKScene in the target scene's didMoveToView:, removing it in willMoveFromView:.
And that is the target scene code I implemented:
#interface MQSSMakerScene ()
#property BOOL contentCreated;
#property MQSSMakerWorkspaceLayer *workspaceLayer;
#property MQSSMakerDockLayer *dockLayer;
#property UIScrollView *scrollView;
#end
#implementation MQSSMakerScene
- (id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size]) {
CGSize layerSize = CGSizeMake(944, 140);
CGPoint layerPosition = CGPointMake(40, 768-(140+30));
CGRect viewFrame = CGRectMake(layerPosition.x, layerPosition.y, layerSize.width, layerSize.height);
_scrollView = [[UIScrollView alloc] initWithFrame:viewFrame];
_scrollView.contentSize = CGSizeMake(2000, 120);
_scrollView.scrollEnabled = YES;
_scrollView.showsHorizontalScrollIndicator = YES;
_scrollView.backgroundColor = [UIColor brownColor];
}
return self;
}
- (void)didMoveToView:(SKView *)view
{
[super didMoveToView:view];
if(!self.contentCreated) {
[self createSceneContents];
self.contentCreated = YES;
}
[self.view addSubview:_scrollView]; // --> WHERE IT'S BEING ADDED
}
- (void)willMoveFromView:(SKView *)view
{
[super willMoveFromView:view];
[_scrollView removeFromSuperview]; // --> WHERE IT'S BEING REMOVED
}
- (void)createSceneContents
{
self.backgroundColor = [UIColor colorWithHue:.237 saturation:.56 brightness:.46 alpha:1];
self.scaleMode = SKSceneScaleModeAspectFill;
[self addChild:[self newMakerLayout]];
}
- (SKNode *)newMakerLayout
{
SKNode *mainLayout = [[SKNode alloc] init];
self.workspaceLayer = [[MQSSMakerWorkspaceLayer alloc] init];
self.dockLayer = [[MQSSMakerDockLayer alloc] init];
[mainLayout addChild:self.workspaceLayer];
[mainLayout addChild:self.dockLayer];
MQSSStoryObject *ob1 = [[MQSSStoryObject alloc] initWithImageNamed:#"maker-1.png"];
[self.workspaceLayer addChild:ob1];
return mainLayout;
}
#end
Please note that the user should go to this scene from the home scene when clicking on a button. The problem now is that once I add the line:
[self.view addSubview:_scrollView];
to add the UIScrollView object to this scene, the application no more goes to this scene and instead add the UIScrollView to the home scene I am transitioning from. It also doesn't remove it when transitioning to another scene. Once I comment out this line, everything works just as expected, clearly without presenting the UIScrollView.
I am stuck. Any help or advice is highly appreciated. Thanks!
I finally figured out what the problem was for me. I was using the -(void)willLayoutSubviews method of the viewController to ensure I had the correct bounds, but I had forgotten to see if my SKScene was already presented. That resulted in a new welcomeScene being presented each time the view was laid out on top of the old one.
-(void)willLayoutSubviews
{
if(!self.didPresentScene)
{
// Present scene here
....
self.didPresentScene = YES;
}
}
I want to have a persistent button in the bottom right corner of my app. During all view transitions, the button should remain static. I'm having trouble deciding what view to add the button to. I know the button ought to be stored in the AppDelegate, but I don't know what other view it would be sense to add it to except the window. One downside of adding it to the window is that when there's an app running in the background (ie Phone), the added status bar padding will push down the window. In general, adding it to the window seems to be a hacky solution -- any thoughts?
Yes, adding it to the UIWindow would be extremely hacky and finicky.
Storyboards
If you're using Storyboards and iOS 5.0 onwards, you should be able to use container views and do something like this:
Here's another picture showing the, rather simplistic, structure of the first View Controller:
The view controller on the left has a container, and then a view which holds the button on top of it. The container indicates that the navigation controller (directly to the right) should appear within itself, that relationship is shown by the =([])=> arrow (formally known as an embed segue). Finally the navigation controller defines its root view controller to the one on the right.
In summary, the first view controller pancakes-in the container view with the button on top, so everything that happens inside has to have the button on top.
Using childViewControllers
aka. The "I hate Storyboards and puppies" mode
Using a similar structure to the Storyboard version, you could create the base view controller with its button, and then, add the view that will become then new "root" of the application, underneath.
To make it clear, let's call the one view controller that holds the button the FakeRootViewController, and the view controller that will be, for all practical purposes, the root of the application: RootViewController. All subsequent view controllers won't even know that there's the FakeRootViewController above everyone else.
FakeRootViewController.m
// The "real" root
#import "RootViewController.h"
// Call once after the view has been set up (either through nib or coded).
- (void)setupRootViewController
{
// Instantiate what will become the new root
RootViewController *root = [[RootViewController alloc] <#initWith...#>];
// Create the Navigation Controller
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:root];
// Add its view beneath all ours (including the button we made)
[self addChildViewController:nav];
[self.view insertSubview:nav.view atIndex:0];
[nav didMoveToParentViewController:self];
}
AppDelegate.m
#import "FakeRootViewController.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
FakeRootViewController *fakeRoot = [[FakeRootViewController alloc] <#initWith...#>];
self.window.rootViewController = fakeRoot;
[self.window makeKeyAndVisible];
return YES;
}
That way, you can have all the benefits of inserting the button on the window, without all the guilt and "Should I really be a programmer?" that it causes.
Potentially you could have 1 main "root" view controller, and all you other view controllers could be child view controllers, with their views as child views. Then they would have their content, and the button would be in the "root" view controller. But this seems just as sketchy and hacky as putting it in the window, and probably less convenient.
I use this button:
#interface UIPopUpButton : UIImageView <UIPopoverControllerDelegate, UIActionSheetDelegate>
{
UIPopoverController* popoverController;
Class popoverClass;
}
- (id) initWithPoint: (CGPoint) point;
- (void) touchesBegan: (NSSet*) touches
withEvent: (UIEvent*) event;
+ (id) buttonAtPoint: (CGPoint) point;
+ (id) buttonAtOriginalPoint;
+ (void) unhighlight;
+ (void) bringButtonToFront;
#property (nonatomic, retain) UIPopoverController* popoverController;
#property (nonatomic, assign) Class popoverClass;
#end
#import "UIPopUpButton.h"
#implementation UIPopUpButton
static UIPopUpButton* button = nil;
static CGPoint originalPoint;
#synthesize popoverClass;
#synthesize popoverController;
+ (id) buttonAtPoint: (CGPoint) point
{
if (button == nil)
{
button = [[UIPopUpButton alloc] initWithPoint: point];
originalPoint = point;
button.popoverClass = [UIPopoverController class];
}
else
{
button.frame = CGRectMake(point.x, point.y, button.frame.size.width, button.frame.size.height);
}
return button;
}
+ (id) buttonAtOriginalPoint
{
return [self buttonAtPoint: originalPoint];
}
+ (void) unhighlight
{
button.highlighted = NO;
}
+ (void) bringButtonToFront
{
[[UIApplication sharedApplication].keyWindow addSubview: [self buttonAtOriginalPoint]];
}
- (id) initWithPoint: (CGPoint) point
{
UIImage* image1 = [UIImage imageNamed: #"topbutton.png"];
UIImage* image2 = [UIImage imageNamed: #"topbutton.png"];
if ((self = [super initWithImage: image1
highlightedImage: image2]))
{
self.userInteractionEnabled = YES;
self.frame = CGRectMake(point.x, point.y, self.frame.size.width, self.frame.size.height);
self.multipleTouchEnabled = NO;
}
return self;
}
- (BOOL) isAppCurrStatus
{
return ([DevToolsClientController sharedInstance].statusOfRootViewController == FrontEndApplication);
}
- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event
{
UITouch* touch = [touches anyObject];
if(touch.view == self)
{
if (self.popoverController == nil)
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
UIActionSheet* actionSheet = [[UIActionSheet alloc] initWithTitle: #"Please choice operation:"
delegate: self
cancelButtonTitle: nil
destructiveButtonTitle: nil
otherButtonTitles: nil];
[actionSheet addButtonWithTitle: #"Cancel"];
actionSheet.cancelButtonIndex = 0;
[actionSheet addButtonWithTitle: #"Button 1"];
actionSheet.actionSheetStyle = UIActionSheetStyleDefault;
[actionSheet setTag: 0];
[actionSheet setDelegate: self];
[actionSheet showInView: [self superview]];
[actionSheet release];
[actions release];
}
else
{
PopoverMenuController* contentViewController = [[PopoverMenuController alloc] init];
self.popoverController = [[UIPopoverController alloc] initWithContentViewController: contentViewController];
popoverController.delegate = self;
[popoverController presentPopoverFromRect: CGRectMake(10.0f, 10.0f, 5.0f, 5.0f)
inView: self
permittedArrowDirections: UIPopoverArrowDirectionAny
animated: YES];
contentViewController.popoverController = self.popoverController;
[contentViewController reloadData];
}
}
else
{
[self.popoverController dismissPopoverAnimated:YES];
self.popoverController = nil;
}
}
[super touchesBegan: touches withEvent: event];
}
#pragma mark UIActionSheetDelegate implementation
-(void) actionSheet: (UIActionSheet*) actionSheet clickedButtonAtIndex: (NSInteger) buttonIndex
{
NSNumber* indexAction = [[NSNumber alloc] initWithInt: buttonIndex - 1];
}
- (void) runAction: (NSNumber*) indexAction
{
[DevToolsPopoverMenuController runAction: [indexAction integerValue]];
}
#pragma mark -
#pragma mark UIPopoverControllerDelegate implementation
- (void) popoverControllerDidDismissPopover: (UIPopoverController*) thePopoverController
{
if (self.popoverController != nil)
{
self.popoverController = nil;
}
}
- (BOOL) popoverControllerShouldDismissPopover: (UIPopoverController*) thePopoverController
{
//The popover is automatically dismissed if you click outside it, unless you return NO here
return YES;
}
#end
call:
[UIPopUpButton bringButtonToFront];
My button is always on top.
Try subclassing the UIViewController class and make your own one with the button
Create a singleton object that holds the button so all view controllers can reference it and add it to their subview or add it to the window directly.
SomeClass.h
#property (nonatomic) UIButton *yourButton;
+(SomeClass*)sharedSomeClass;
SomeClass.m
#synthesize yourButton = _yourButton;
-(id)init
{
self = [super init];
if(self)
{
_yourButton = [UIButton new];
//Other settings you want for your button
}
return self;
}
+(SomeClass)sharedSomeClass
{
static SomeClass *sharedSomeClass;
if (!sharedSomeClass)
sharedSomeClass = [[super allocWithZone:nil]init];
return sharedSomeClass;
}
+(void)allocWithZone:(NSZone*)zone
{
return [self sharedSomeClass];
}
If you like you can access the window directly like this:
UIWindow *mainwindow = [[[UIApplication sharedApplication]delegate]window];
import SomeClass.h into your view controllers, and access the button from anywhere
#import "SomeClass.h"
SomeClass *someClass = [SomeClass sharedSomeclass];
UIButton *localButton = someClass.yourButton;