Full Screen Custom Popover issue in iOS7 and iOS8 - ios

I am developing an app for iPad only. In which in for one functionality i want to display FullCustom Popover.
For that my code is as below:-
DuplicateViewController *viewControllerForPopover =
[self.storyboard instantiateViewControllerWithIdentifier:#"DuplicatePopoverVC"];
viewControllerForPopover.arr_studentDetail = self.arrStudentDetail;
viewControllerForPopover.dictSelectedProg = dictSelectedProgram;
self.popover = [[UIPopoverController alloc]
initWithContentViewController:viewControllerForPopover];
[self.popover setPopoverContentSize:CGSizeMake(self.view.frame.size.width, self.view.frame.size.height)];
viewControllerForPopover.modalPresentationStyle = UIModalPresentationFullScreen;
[self.popover setBackgroundColor:[[UIColor darkGrayColor] colorWithAlphaComponent:0.4]];
[self.popover presentPopoverFromRect:self.view.bounds inView:self.view permittedArrowDirections:0 animated:YES];
I set popover size and tried with other option but can't make it full screen.
ViewDidLoad -> DuplicateViewController
[view_main.layer setBorderWidth:5.0f];
[view_main.layer setCornerRadius:25.0f];
[view_main.layer setBorderColor:[UIColor colorWithRed:(29.0f/255.0f) green:134.0f/255.0f blue:140.0f/255.0f alpha:1.0f].CGColor];
But while running App, It display as below:-
Please help me to display full screen Popover. Thank you so much in advance.

create two classes
the first one should inherit UIPopoverController
the second one should inherit UIPopoverBackgroundView
for example:
CustomUIPopoverController.h
#interface CustomUIPopoverController : UIPopoverController
CustomUIPopoverController.m
#implementation CustomUIPopoverController
- (id)initWithContentViewController:(UIViewController *)viewController {
self = [super initWithContentViewController:viewController];
if (self) {
self.popoverBackgroundViewClass = [CustomUIClearPopoverBackgroundView class];
[self setPopoverContentSize:viewController.view.frame.size];
}
return self;
}
#end
CustomUIClearPopoverBackgroundView.h
#interface CustomUIClearPopoverBackgroundView : UIPopoverBackgroundView
CustomUIClearPopoverBackgroundView.m
#import "CustomUIClearPopoverBackgroundView.h"
#implementation CustomUIClearPopoverBackgroundView
#pragma mark - no arrow
+ (CGFloat)arrowHeight {
return 0;
}
+ (CGFloat)arrowBase {
return 0;
}
- (CGFloat)arrowOffset {
return 0;
}
- (void)setArrowOffset:(CGFloat)arrowOffset {
}
- (UIPopoverArrowDirection)arrowDirection {
return 0;
}
- (void)setArrowDirection:(UIPopoverArrowDirection)arrowDirection {
}
#pragma mark - no margins
+ (UIEdgeInsets)contentViewInsets {
return UIEdgeInsetsZero;
}
#pragma mark - fully transparent (default is 0.15)
+(BOOL)wantsDefaultContentAppearance {
return NO;
}
- (void)willMoveToWindow:(UIWindow *)newWindow {
[super willMoveToWindow:newWindow];
// hide shadow image view
[self.superview.subviews[0] setHidden:YES];
}
#end
I used this but I don't like it very much.
the second option is to create a container view and hide/show it as you like.
this way it's a pure view controller and you don't need to mess with popup stuff
to create the transparency, make your container view background = clear, the view controller view background color = clear, and add a semi-transparent button in the size of the view controller for the semi-transparent black background. on click of the button close the window (= same effect as tapping the popup background)

Related

iOS - Switch between dismiss and scroll gestures

There's a behavior in the Line messenger app (the de facto messenger app in Japan) that I'm trying to emulate.
Basically, they have a modal view controller with a scroll view inside. When the scroll action reaches the top of its content, the view controller seamlessly switches to an interactive dismissal animation. Also, when the gesture returns the view to the top of the screen, control is returned to the scroll view.
Here's a gif of how it looks.
For the life of me, I can't figure out how they did it. I've tried a few different methods, but they've all failed, and I'm out of ideas. Can anyone point me in the right direction?
EDIT2
To clarify, the behavior that I want to emulate isn't just simply dragging the window down. I can do that, no problem.
I want to know how the same scroll gesture (without lifting the finger) triggers the dismissal transition and then transfers control back to the scroll view after the view has been dragged back to the original position.
This is the part that I can't figure out.
End EDIT2
EDIT1
Here's what I have so far. I was able to use the scroll view delegate methods to add a target-selector that handles the regular dismissal animation, but it still doesn't work as expected.
I create a UIViewController with a UIWebView as a property. Then I put it in a UINavigationController, which is presented modally.
The navigation controller uses animation/transition controllers for the regular interactive dismissal (which can be done by gesturing over the navigation bar).
From here, everything works fine, but the dismissal can't be triggered from the scroll view.
NavigationController.h
#interface NavigationController : UINavigationController <UIViewControllerTransitioningDelegate>
#property (nonatomic, strong) UIPanGestureRecognizer *gestureRecog;
- (void)handleGesture:(UIPanGestureRecognizer*)gestureRecognizer;
#end
NavigationController.m
#import "NavigationController.h"
#import "AnimationController.h"
#import "TransitionController.h"
#implementation NavigationController {
AnimationController *_animator;
TransitionController *_interactor;
}
- (instancetype)init {
self = [super init];
self.transitioningDelegate = self;
_animator = [[AnimationController alloc] init];
_interactor = [[TransitionController alloc] init];
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Set the gesture recognizer
self.gestureRecog = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
[self.view addGestureRecognizer:_gestureRecog];
}
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator {
if (animator == _animator && _interactor.hasStarted) {
return _interactor;
}
return nil;
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
if (dismissed == self || [self.viewControllers indexOfObject:dismissed] != NSNotFound) {
return _animator;
}
return nil;
}
- (void)handleGesture:(UIPanGestureRecognizer *)gestureRecog {
CGFloat threshold = 0.3f;
CGPoint translation = [gestureRecog translationInView:self.view];
CGFloat verticalMovement = translation.y / self.view.bounds.size.height;
CGFloat downwardMovement = fmaxf(verticalMovement, 0.0f);
CGFloat downwardMovementPercent = fminf(downwardMovement, 1.0f);
switch (gestureRecog.state) {
case UIGestureRecognizerStateBegan: {
_interactor.hasStarted = YES;
[self dismissViewControllerAnimated:YES completion:nil];
break;
}
case UIGestureRecognizerStateChanged: {
if (!_interactor.hasStarted) {
_interactor.hasStarted = YES;
[self dismissViewControllerAnimated:YES completion:nil];
}
_interactor.shouldFinish = downwardMovementPercent > threshold;
[_interactor updateInteractiveTransition:downwardMovementPercent];
break;
}
case UIGestureRecognizerStateCancelled: {
_interactor.hasStarted = NO;
[_interactor cancelInteractiveTransition];
break;
}
case UIGestureRecognizerStateEnded: {
_interactor.hasStarted = NO;
if (_interactor.shouldFinish) {
[_interactor finishInteractiveTransition];
} else {
[_interactor cancelInteractiveTransition];
}
break;
}
default: {
break;
}
}
}
#end
Now, I have to get that gesture handling to trigger when the scroll view has reached the top. So, here's what I did in the view controller.
WebViewController.m
#import "WebViewController.h"
#import "NavigationController.h"
#interface WebViewController ()
#property (weak, nonatomic) IBOutlet UIWebView *webView;
#end
#implementation WebViewController {
BOOL _isHandlingPan;
CGPoint _topContentOffset;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.webView.scrollView setDelegate:self];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if ((scrollView.panGestureRecognizer.state == UIGestureRecognizerStateBegan ||
scrollView.panGestureRecognizer.state == UIGestureRecognizerStateChanged) &&
! _isHandlingPan &&
scrollView.contentOffset.y < self.navigationController.navigationBar.translucent ? -64.0f : 0) {
NSLog(#"Adding scroll target");
_topContentOffset = CGPointMake(scrollView.contentOffset.x, self.navigationController.navigationBar.translucent ? -64.0f : 0);
_isHandlingPan = YES;
[scrollView.panGestureRecognizer addTarget:self action:#selector(handleGesture:)];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
NSLog(#"Did End Dragging");
if (_isHandlingPan) {
NSLog(#"Removing action");
_isHandlingPan = NO;
[scrollView.panGestureRecognizer removeTarget:self action:#selector(handleGesture:)];
}
}
- (void)handleGesture:(UIPanGestureRecognizer*)gestureRecognizer {
[(NavigationController*)self.navigationController handleGesture:gestureRecognizer];
}
This still doesn't work quite right. Even during the dismissal animation, the scroll view is still scrolling with the gesture.
End EDIT1
That is a custom interactive transition.
First, you need set transitioningDelegate of UIViewController
id<UIViewControllerTransitioningDelegate> transitioningDelegate;
Then implment these two method to
//Asks your delegate for the transition animator object to use when dismissing a view controller.
- animationControllerForDismissedController:
//Asks your delegate for the interactive animator object to use when dismissing a view controller.
- interactionControllerForDismissal:
When drag to top, you start the transition, you may use UIPercentDrivenInteractiveTransition to control the progress during scrolling.
You can also refer to the source code of ZFDragableModalTransition
Image of ZFDragableModalTransition
As explained here the solution is quite complex. The person who answered, #trungduc, programmed a little demo published on github doing the sought behaviour. You can find it here.
The easiest way of making this work is to copy the 4 files found in /TestPanel/Presentation/ in the attached github repository, to your project. Then add the PanelAnimationControllerDelegate to your View Controller containing the scroll view (i.e. using the protocol).
Add the following to your View Controller, to satisfy the protocol:
func shouldHandlePanelInteractionGesture() -> Bool {
return (scrollView.contentOffset.y == 0);
}
Add this to deactivate the bouncing effect at the top:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollView.bounces = (scrollView.contentOffset.y > 10);
}
Set scrollView.delegate = self
Before presenting your View Controller containing the scroll view set the following propreties to your View Controller:
ScrollViewController.transitioningDelegate = self.panelTransitioningDelegate
ScrollViewController.modalPresentationStyle = .custom
If you want to change the size of your ScrollViewController, you will need to comment out the override of the frameOfPresentedViewInContainerView in the PanelPresentationController file (one of the 4). Then in the presentationTransitionWillBegin method, you will need to set let frameOfPresentedViewInContainerView = self.frameOfPresentedViewInContainerView.insetBy(dx: 0, dy: 20) with the wanted inset of dx and dy.
Thank you to trungduc for this amazing solution!!

How to change the color of navigation bar on a specific view

I need to change the color of my navigation bar on only some specific views.
There are many discussions about modifying the color of navigation bar, like https://stackoverflow.com/a/18870519/938380, but they all change the color of navigation bar on every views under the same navigation hierarchy.
I want to change the color on specific views and keep other views the same color. How do I achieve this?
For Swift 4 use
override func willMove(toParentViewController parent: UIViewController?) {
self.navigationController?.navigationBar.barTintColor = .red
}
It gives a cleaner animation
if you use standart navigation bar, you can't do it. But you can use some cheat ;) For example, you can add this code(or something like this) to your controller:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
oldColor = self.navigationController.navigationBar.backgroundColor;//probably barTintColor instead of backgroundColor
self.navigationController.navigationBar.backgroundColor = [UIColor yellowColor];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.navigationController.navigationBar.backgroundColor = oldColor;
}
You Can Set Bar Tint Property OF navigationController
I hope this one help you
[self.navigationController.navigationBar setBarTintColor:[UIColor redColor]];
I handle this kind of thing through an extension to UIViewController. In my case I want to make selected navigation bars transparent. You could extend this to deal with colors. This saves pasting the same code in multiple controllers. Code below.
From viewWillAppear you call [self makeNavigationBarTransparent]. From viewWillDisappear you call [self restoreNavigationBar]
For your case you would simply extend this to add makeNavigationBarColored.
An option is to also sub class UINavigationController to create a class which has a particular color. In its implementation use this extension to set the color. In your storyboard make any controllers you want this color be of your new class type. Then you are doing all this from storyboard.
UIViewController+TransparentNavigationBar.h
#import <UIKit/UIKit.h>
#interface UIViewController (TransparentNavigationBar)
/** Makes the current navigation bar transparent and returns a context holding
* the original settings.
*/
- (void) makeNavigationBarTransparent;
/**
* Restores the current navigation bar to its original settings.
*/
- (void) restoreNavigationBar;
#end
UIViewController+TransparentNavigationController.m
#import "UIViewController+TransparentNavigationBar.h"
#import <objc/runtime.h>
#interface VSSNavigationBarContext:NSObject
/** Backup of nav bar image used to restore on push. */
#property UIImage *originalNavBarBackgroundImage;
/** Backup of nav bar shadow image used to restore on push. */
#property UIImage *originalNavBarShadowImage;
/** Backup of nav bar color used to restore on push. */
#property UIColor *originalNavBarColour;
#end
#implementation VSSNavigationBarContext
- (id) init {
self=[super init];
if (self){
self.originalNavBarBackgroundImage=nil;
self.originalNavBarShadowImage=nil;
self.originalNavBarColour=nil;
}
return self;
}
#end
static char const * const ObjectTagKey = "NavBarContextTag";
#implementation UIViewController (TransparentNavigationBar)
- (VSSNavigationBarContext *) getContext
{
VSSNavigationBarContext *context=(VSSNavigationBarContext *)objc_getAssociatedObject(self, &ObjectTagKey);
return context;
}
- (void) makeNavigationBarTransparent{
VSSNavigationBarContext *context=(VSSNavigationBarContext *)objc_getAssociatedObject(self, &ObjectTagKey);
if (context == nil){
context=[[VSSNavigationBarContext alloc] init];
context.originalNavBarBackgroundImage=[self.navigationController.navigationBar backgroundImageForBarMetrics:UIBarMetricsDefault];
context.originalNavBarShadowImage=self.navigationController.navigationBar.shadowImage;
context.originalNavBarColour=self.navigationController.view.backgroundColor;
// Store the original settings
objc_setAssociatedObject(self, &ObjectTagKey, context, OBJC_ASSOCIATION_RETAIN);
}
//
// Make transparent
//
[self.navigationController.navigationBar setBackgroundImage:[UIImage new]
forBarMetrics:UIBarMetricsDefault];
self.navigationController.navigationBar.shadowImage = [UIImage new];
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0f){
self.navigationController.navigationBar.translucent = YES;
}
else{
self.navigationController.navigationBar.translucent = NO;
}
self.navigationController.view.backgroundColor = [UIColor clearColor];
}
- (void) restoreNavigationBar
{
VSSNavigationBarContext *context=(VSSNavigationBarContext *)objc_getAssociatedObject(self, &ObjectTagKey);
if (context != nil){
// Restore original
[self.navigationController.navigationBar setBackgroundImage:context.originalNavBarBackgroundImage forBarMetrics:UIBarMetricsDefault];
self.navigationController.navigationBar.shadowImage = context.originalNavBarShadowImage;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0f){
self.navigationController.navigationBar.translucent = YES;
}
else{
self.navigationController.navigationBar.translucent = NO;
}
self.navigationController.view.backgroundColor = context.originalNavBarColour;
}
}
#end

Adjust the BarButtonItem position in UINavigationBar

I need to adjust the vertical position of my UIBarButtonItem in UINavigationBar.I think there is a way to do this. To add a UIView to UIBarButtonItem and add a UIButton to the UIView. It's ok if you have only one or two navigation bar. But I think it's a bit too troublesome if you have dozens of UINavigationBar especially inside a storyboard. So I did some researches and found a easy solution for this. That is to use category. Here is my source code:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
-(void)viewWillAppear:(BOOL)animated
{
[self show:NO];
}
// do your override
-(void)viewWillLayoutSubviews
{
[self changeTheButtonPosition:self.navigationItem.leftBarButtonItem];
[self changeTheButtonPosition:self.navigationItem.rightBarButtonItem];
}
-(void)viewDidAppear:(BOOL)animated
{
[self show:YES];
}
#pragma clang diagnostic pop
-(void)show:(BOOL)show
{
self.navigationItem.leftBarButtonItem.customView.hidden = !show;
self.navigationItem.rightBarButtonItem.customView.hidden = !show;
}
- (void)changeTheButtonPosition:(UIBarButtonItem *)barButtonItem
{
if ( barButtonItem )
{
UIView* customView = barButtonItem.customView;
// NSLog(#"custom view frame = %#",NSStringFromCGRect(customView.frame));
CGRect frame = customView.frame;
CGFloat y = SYSTEM_VERSION_LESS_THAN(#"7") ? 10.0f : 5.0f;
customView.frame = CGRectMake(frame.origin.x, y, frame.size.width, frame.size.height);
}
}
It works perfectly in iOS 7 and in the first view controller in iOS 6. But it doesn't work for the pushed UIViewController in iOS 6. I can't find any reason for that. Can anybody advise? What's wrong in my code in iOS 6?
You should be able to achieve this using the UIAppearance proxy methods introduced in iOS5. In particular:
- (void)setBackgroundVerticalPositionAdjustment:(CGFloat)adjustment forBarMetrics:(UIBarMetrics)barMetrics
- (void)setTitlePositionAdjustment:(UIOffset)adjustment forBarMetrics:(UIBarMetrics)barMetrics
Check the UIBarButtonItem docs for more info.

UIImagePickerController not full screen

Since the iOS7 upgrade, I have a weird behaviour of the UIImagePickerController. In this application I am using the UIImagePickerController with a cameraOverlayView.
In iOS6 I called the UIImagePickerController using the following code:
_picker = [[UIImagePickerController alloc] init];
if ([UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear]) {
_picker.sourceType = UIImagePickerControllerSourceTypeCamera;
_picker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
_picker.cameraDevice = UIImagePickerControllerCameraDeviceRear;
_picker.showsCameraControls = NO;
_picker.navigationBarHidden = NO;
_picker.toolbarHidden = YES;
_picker.wantsFullScreenLayout = YES;
_overlayViewController = [[OverlayViewController alloc] init];
_overlayViewController.picker = _picker;
_overlayViewController.frameSize = self.frameSize;
_overlayViewController.delegate = self;
_picker.cameraOverlayView = _overlayViewController.view;
}
else {
_picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
}
_picker.delegate = self;
Where the OverlayViewController is an UIViewController, with a transparent background which draws some custom controls on screen.
But now in iOS 7 the camera is drawn through the statusbar and a black bar appears beneath the live camera view.
I can solve this by applying a CGAffineTransformMakeTranslation to the cameraViewTransform property of the UIImagePickerController, but why is this like this?
In iOS 7, by default UIViewController views take up the entire screen area including the status bar.
wantsFullScreenLayout
is deprecated and ignored. In some cases, this fix works (in the view controller class):
if ([self respondsToSelector:#selector(setEdgesForExtendedLayout:)]) {
[self setEdgesForExtendedLayout:UIRectEdgeNone];
}
In other cases, it's a bit more complicated. It's late here, so see how you go with it. Helpful things to note - in a UIViewController, the following code will give the correct statusbar height on both iOS 6 and iOS 7, should it come to having to align things using CGRect math:
if (UIDeviceOrientationIsLandscape(self.interfaceOrientation)) {
statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.width;
} else {
statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height;
}
And then don't forget that in Interface Builder, there are the new "iOS 6 delta" adjustments that allow you to design for iOS 7 and then use offsets to correct for iOS 6.
Anyhow, let me know how you go.
My understanding of the issue, based on a few other SO threads and such, is that UIImagePickerController does not do what we'd expect in terms of managing the status bar via [UIViewController -prefersStatusBarHidden].
This means you either have to disable view controller status bar management entirely, via plist, or figure out a way to get UIImagePickerController to do what we want. On the assumption that you're not looking for the former, I can say I've had success in the latter by putting the picker in a wrapper controller that does what I want (but fall back to your previous code if you still need to detect/support iOS6):
#interface PickerContainer : UIViewController
#property ( nonatomic, weak ) UIImagePickerController* picker;
#end
#implementation PickerContainer
- (void) setPicker: (UIImagePickerController*) picker
{
[self addChildViewController: picker];
[picker didMoveToParentViewController: self];
self->_picker = picker;
}
- (void) viewDidLoad
{
[super viewDidLoad];
self.picker.view.frame = self.view.bounds;
[self.view addSubview: self.picker.view];
}
// Will have no effect in ios6 -- see [-init] for that option
- (BOOL) prefersStatusBarHidden { return YES; }
- (id) init
{
if ( ! ( self = [super init] ) ) return nil;
if ( detectThatThisIsIos6() ) self.wantsFullScreenLayout = YES;
return self;
}
#end
This will work for you, scaled camera, you will have a black bar at the bottom but it will get overlayed by tool bar
https://stackoverflow.com/a/15803947

How to create popover in iPhone app?

I'm looking for popover in iPhone and i want to make it like iOS 5 Reader feature:
After little research i found WEPopover and FPPopover but i'm looking if there anything like this API built-in iphone SDK.
You could make a UIView with some custom artwork and display it with an animation on top of your view as a "popover" with some buttons like so:
UIView *customView = [[UIView alloc] initWithFrame:CGRectMake(25, 25, 100, 50)]; //<- change to where you want it to show.
//Set the customView properties
customView.alpha = 0.0;
customView.layer.cornerRadius = 5;
customView.layer.borderWidth = 1.5f;
customView.layer.masksToBounds = YES;
//Add the customView to the current view
[self.view addSubview:customView];
//Display the customView with animation
[UIView animateWithDuration:0.4 animations:^{
[customView setAlpha:1.0];
} completion:^(BOOL finished) {}];
Don't forget to #import <QuartzCore/QuartzCore.h>, if you want to use the customView.layer.
Since iOS8 we are now able to create popovers, that will be the same on iPhone, as on iPad, which would be especially awesome for those who make universal apps, thus no need to make separate views or code.
You can get the class as well as demo project here: https://github.com/soberman/ARSPopover
All you need to do is subclass UIViewController, conform to the UIPopoverPresentationControllerDelegate protocol and set desired modalPresentationStyle along with the delegate value:
// This is your CustomPopoverController.m
#interface CustomPopoverController () <UIPopoverPresentationControllerDelegate>
#end
#implementation CustomPopoverController.m
- (instancetype)init {
if (self = [super init]) {
self.modalPresentationStyle = UIModalPresentationPopover;
self.popoverPresentationController.delegate = self;
}
return self;
}
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller {
return UIModalPresentationNone; //You have to specify this particular value in order to make it work on iPhone.
}
Afterwards, instantiate your newly created subclass in the method from which you want to show it and assign two more values to sourceView and sourceRect. It looks like this:
CustomPopoverController *popoverController = [[CustomPopoverController alloc] init];
popoverController.popoverPresentationController.sourceView = sourceView; //The view containing the anchor rectangle for the popover.
popoverController.popoverPresentationController.sourceRect = CGRectMake(384, 40, 0, 0); //The rectangle in the specified view in which to anchor the popover.
[self presentViewController:popoverController animated:YES completion:nil];
And there you have it, nice, neat blurred popover.

Resources