Why ViewDidApper and ViewWillApper are not always called? - ios

I have a method that sets the height of a UIScrollView and a UITextView based on the content of the UITextView. I realized that both, textview and scrollview are correctly resized when the method is call from viewWillAppear but not from viewDidLoad (the textview is not correctly resized from viewDidLoad but the scrollview it is).
//In View1
-(void) setHeight
{
NSLog(#"Set height");
CGRect frame = descriptionTextView.frame;
frame.size.height = descriptionTextView.contentSize.height;
descriptionTextView.frame = frame;
if([[UIScreen mainScreen] bounds].size.height == 568) //iPhone 4inch
{
totalHeight = 380+frame.size.height;
[self.mainScrollView setContentSize:CGSizeMake(320,totalHeight)];
}
else{
totalHeight = 250+frame.size.height;
[self.mainScrollView setContentSize:CGSizeMake(320,totalHeight)];
}
}
The problem is that I created a custom tab bar menu with a UIView that's a placeholder. The first time I perform the segue it works, but not if I go back and press the tab again, since viewWillAppear and viewDidAppear are not called. why are not called? How can I force them to be called?
//In CustomTabBarController
-(void) selectTab{
self.isLoaded = YES;
self.activity_indicator.hidden = YES;
if ([self.selectedButton isEqualToString: #"view1"])
[self performSegueWithIdentifier:#"sg_view1" sender:self];
else if ([self.selectedButton isEqualToString:#"view2" ])
[self performSegueWithIdentifier:#"sg_view2" sender:self];
else if ([self.selectedButton isEqualToString:#"view3" ])
[self performSegueWithIdentifier:#"sg_view3" sender:self];
}

The best approach would be to using the tab bar delegate method instead of viewWillAppear:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
if ([viewController isKindOfClass:someClass]){
//do your stuff
}
}

Related

Scrolling a UIView along with UITableView scroll

I have a tableview in which UITableViewCell contains a textfield. When user tap on the textfield and keyboard appears, I scroll to the tapped in row after setting proper content inset.
Now, I have a UIView shown on top of my table view which I need to adjust accordingly when table scrolls.
To do this, I implemented scrollViewDidScroll: method as below. While this works in certain situations, it do not work or give random results in some situations. Am I doing something wrong here?
- (void)scrollViewDidScroll:(UIScrollView *)iScrollView {
if (self.hollowView) {
CGPoint offset = iScrollView.contentOffset;
CGRect hollowViewFrame = self.hollowView.hollowFrame;
hollowViewFrame.origin.y += self.previousOffset - offset.y;
self.previousOffset = offset.y;
[self.hollowView resetToFrame:hollowViewFrame];
}
}
- (void)keyboardDidHide:(NSNotification *)iNotification {
self.previousOffset = 0.0;
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidHideNotification object:nil];
}
If you want to scroll the both view simultaneously then you should have to take both views with same size and in following method just set the scrollview content offset to them. It will surly work
enter code here - (void)scrollViewDidScroll:(UIScrollView *)iScrollView {
if (self.scrollView) // Check for the scrolled view
{
// Set the Tableview offset directly
tableview.contentOffset = iScrollView.contentOffset
}
else {
//Set view scroll as per tableview scroll
view.contentOffset = iScrollView.contentOffset } }
//Note :- Make sure view should be scrollview type or if view is getting scared or shows black screen
Thanks..
Use this library:
https://github.com/hackiftekhar/IQKeyboardManager
It will handle the position of Views when tapped on a textfield.
So, this piece worked for me for anyone who is struggling with same situation:
- (void)scrollViewDidScroll:(UIScrollView *)iScrollView {
if (self.hollowView) {
CGRect frame = [self.tableView rectForRowAtIndexPath:self.expandedCellIndexPath];
frame.origin.y -= self.tableView.contentOffset.y;
[self.hollowView resetToFrame:frame];
}
}
you can use this for scrolling table in ios.
-(void )keyboardWillShow:(NSNotification *)notification
{
NSDictionary* keyboardInfo = [notification userInfo];
NSValue* keyboardFrameBegin =[keyboardInfovalueForKey:UIKeyboardFrameEndUserInfoKey];
keyBoardHeight = keyboardFrameBegin.CGRectValue.size.height;
vwSendBottomSpaceContraint.constant = keyBoardHeight
[self performSelector:#selector(scrollToBottomAnimated) withObject:nil afterDelay:0.1];
}
-(void)keyboardWillHide:(NSNotification*)notification
{
vwSendBottomSpaceContraint.constant = 0;
[self performSelector:#selector(scrollToBottomAnimated) withObject:nil afterDelay:0.1];
}
-(void)scrollToBottomAnimated
{
NSInteger rows = [tableview numberOfRowsInSection:0];
if(rows > 0)
{
[tableview scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:rows - 1 inSection:0] atScrollPosition:UITableViewScrollPositionBottomanimated:YES];
}
}

Avoid UIPageViewController "over-scrolling"

I have 2 VCs on my UIPageViewController.
I want that if the user is on the 1st view, he won't be able to swipe to the right (just to the left - to the 2nd view), and if he's on the 2nd view he won't be able to swipe to the left (just to the right - to the 1st view).
I want that the user won't be able to scroll - not a black view (what happens when I return nil on: - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController)
The "black view" is likely to be the background of your main view, or the background colour of your UIPageViewController.
You can kill scrolling in one of the directions depending on the ViewController that the PageViewController is currently showing
first set your VC as the delegate for the PageViewController's inner scroll view. You can add a more sophisticated subviews traversal here, this is a simple version:
for (UIView *view in self.pageViewController.view.subviews) {
if ([view isKindOfClass:[UIScrollView class]]) {
[(UIScrollView *)view setDelegate:self];
}
}
then add a property to your VC as this:
#property (nonatomic) CGPoint lastOffset;
and after that implement the following scroll view delegate methods
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
scrollView.scrollEnabled = YES;
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
scrollView.scrollEnabled = YES;
self.lastOffset = scrollView.contentOffset;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGPoint nowOffset = scrollView.contentOffset;
NSLog(#"delta %f", self.lastOffset.x - nowOffset.x);
if ((self.lastOffset.x - nowOffset.x) < 0) {
//uncomment to prevent scroll to left
//scrollView.scrollEnabled = NO;
} else if ((self.lastOffset.x - nowOffset.x) > 0) {
//uncomment to prevent scroll to right
//scrollView.scrollEnabled = NO;
} else {
scrollView.scrollEnabled = YES;
}
}

Fitting subview to it's parent?

I have a problematic UIView called as MyListViewController. It is at the first tab in my application.
I am adding a subview in the viewDidLoad section( there are some constraints defined on the subview ). When I run the application the subview doesn't fit to it's root view well. However, if I navigate to an another tab and then re-click to the first tab, the application fits the subview properly.
Also I can't use [self.detailView setTranslatesAutoresizingMaskIntoConstraints:YES]; because it causes heaps of constraint conflicts.
Anyway, I wrote my custom method, patchSubViewPadding, in order to fit the subView to it's rootView which is defined as detailView. As I stated above if I navigate to one of the other tabs and then navigate to the the first tab the application shows up the subview properly.
#interface MyListViewController ()
#property (strong, nonatomic) CustomViewController *customViewController;
#end
#implementation MyListViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
CustomViewController *viewController = (CustomViewController *)[storyboard instantiateViewControllerWithIdentifier:#"customView"];
self.customViewController = viewController;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
[self.detailView addSubview:self.customViewController.view];
}
-(void)viewDidAppear:(BOOL)animated
{
[CommonUtilities patchSubViewPadding:self.detailView subView:self.customViewController.view padding:0];
}
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[CommonUtilities patchSubViewPadding:self.detailView subView:self.customViewController.view padding:0];
}
#end
And this is my custom method:
+(CGRect)patchSubViewPadding:(UIView *)superView subView:(UIView *)subView padding:(float)padding
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
if (padding == 0)
padding = -4.0f;
CGRect frame = superView.bounds;
frame.origin.y = 0;
frame.size.width = frame.size.width - padding;
subView.frame = frame;
return subView.frame;
}
return subView.frame;
}
I tried the following properties either in viewDidLoad or viewWillLayoutSubviews but it didn't work.
[self.detailView setNeedsLayout];
[self.detailView setNeedsUpdateConstraints];
Any ideas? What am I doing wrong?
Move the method call for patchSubViewPadding: out of viewDidAppear and to the bottom of viewDidLoad.

iOS 7+ Dismiss Modal View Controller and Force Portrait Orientation

I have a UINavigationController as the root view controller of my UIWindow on iOS 7 and iOS 8. From one of its view controllers, I present a fullscreen modal view controller with a cross-dissolve presentation style. This modal view controller should be able to rotate to all orientations, and it works fine.
The problem is when the device is held in a landscape orientation and the modal view controller is dismissed. The view controller which presented the modal only supports portrait orientation, and I've confirmed that UIInterfaceOrientationMaskPortrait is returned to -application:supportedInterfaceOrientationsForWindow:. -shouldAutorotate returns YES, as well. However, the orientation of the presenting view controller, after dismissing the modal, remains landscape. How can I force it to remain in portrait orientation while allowing the modal to take the orientation of the device? My code follows:
App delegate:
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
UINavigationController *navigationController = (UINavigationController *)self.deckController.centerController;
NSArray *viewControllers = [navigationController viewControllers];
UIViewController *top = [viewControllers lastObject];
if (top && [top presentedViewController]) {
UIViewController *presented = [top presentedViewController];
if ([presented respondsToSelector:#selector(isDismissing)] && ![(id)presented isDismissing]) {
top = presented;
}
}
return [top supportedInterfaceOrientations];
}
return (UIInterfaceOrientationMaskLandscapeLeft|UIInterfaceOrientationMaskLandscapeRight);
}
Presenting view controller:
- (BOOL)shouldAutorotate {
return YES;
}
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
Modal view controller:
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
return (UIInterfaceOrientationMaskLandscape|UIInterfaceOrientationMaskLandscapeLeft|UIInterfaceOrientationMaskPortrait);
}
If the modal controller was in landscape orientation before dismissal, the presenting ViewController may not return to the origin orientation (portrait). The problem is because the AppDelegate supportedInterfaceOrientationsForWindow method is called before the controller is actually dismissed and the presented controller check still returns Landscape mask.
Set a flag to indicate whether the (modal) presented view controller will be displayed or not.
- (void)awakeFromNib // or where you instantiate your ViewController from
{
[super awakeFromNib];
self.presented = YES;
}
- (IBAction)exitAction:(id)sender // where you dismiss the modal
{
self.presented = NO;
[self dismissViewControllerAnimated:NO completion:nil];
}
And in the modal presented ViewController set the orientation according to the flag: When the modal ViewController is presented - return Landscape. When it is dismissed then return portrait
- (NSUInteger)supportedInterfaceOrientations
{
if ([self isPresented]) {
return UIInterfaceOrientationMaskLandscape;
} else {
return UIInterfaceOrientationMaskPortrait;
}
}
Last step - from your AppDelegate call the modal presented ViewController for its orientation. I am just checking the currently presented ViewController and call the supportedInterfaceOrientations on it
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
NSUInteger orientationMask = UIInterfaceOrientationMaskPortrait;
UIViewController *currentVC = self.window.rootViewController.presentedViewController; // gets the presented VC
orientationMask = [currentVC supportedInterfaceOrientations];
return orientationMask;
}
For more info check this link
This solution is for iOS 8+.
Problem description
Application key window have UINavigationController's subclass as its rootViewController.
This NC subclass prohibits some of the interface orientations.
Some View Controller (VC1) in the NC stack is presenting another View Controller (VC2) modally and fullscreen.
This presented VC2 allows more interface orientations than NC do.
User rotates device to orientation that is prohibited by NC, but allowed by presented VC2.
User dismisses the presented VC2.
View of VC1 has incorrect frame.
Setup and illustration
UINavigationController's subclass:
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
- (BOOL)shouldAutorotate
{
return YES;
}
VC1 initial appearance and UI view stack:
Presenting VC2 (QLPreviewController in that example) from VC1:
QLPreviewController *pc = [[QLPreviewController alloc] init];
pc.dataSource = self;
pc.delegate = self;
pc.modalPresentationStyle = UIModalPresentationFullScreen;
[self.navigationController presentViewController:pc animated:YES completion:nil];
VC2 is presented and device rotated to landscape:
VC2 dismissed, device is back in portrait mode, but NC stack remains in landscape:
Cause
Apple documentation states:
When you present a view controller using the presentViewController:animated:completion: method, UIKit always manages the presentation process. Part of that process involves creating the presentation controller that is appropriate for the given presentation style.
Apparently there is a bug in handling UINavigationController stack.
Solution
This bug can be bypassed by providing our own transitioning delegate.
BTTransitioningDelegate.h
#import <UIKit/UIKit.h>
#interface BTTransitioningDelegate : NSObject <UIViewControllerTransitioningDelegate>
#end
BTTransitioningDelegate.m
#import "BTTransitioningDelegate.h"
static NSTimeInterval kDuration = 0.5;
// This class handles presentation phase.
#interface BTPresentedAC : NSObject <UIViewControllerAnimatedTransitioning>
#end
#implementation BTPresentedAC
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return kDuration;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)context
{
// presented VC
UIViewController *toVC = [context viewControllerForKey:UITransitionContextToViewControllerKey];
// presented controller ought to be fullscreen
CGRect frame = [[[UIApplication sharedApplication] keyWindow] bounds];
// we will slide view of the presended VC from the bottom of the screen,
// so here we set the initial frame
toVC.view.frame = CGRectMake(frame.origin.x, frame.origin.y + frame.size.height, frame.size.width, frame.size.height);
// [context containerView] acts as the superview for the views involved in the transition
[[context containerView] addSubview:toVC.view];
UIViewAnimationOptions options = (UIViewAnimationOptionCurveEaseOut);
[UIView animateWithDuration:kDuration delay:0 options:options animations:^{
// slide view to position
toVC.view.frame = frame;
} completion:^(BOOL finished) {
// required to notify the system that the transition animation is done
[context completeTransition:finished];
}];
}
#end
// This class handles dismission phase.
#interface BTDismissedAC : NSObject <UIViewControllerAnimatedTransitioning>
#end
#implementation BTDismissedAC
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return kDuration;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)context
{
// presented VC
UIViewController *fromVC = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
// presenting VC
UIViewController *toVC = [context viewControllerForKey:UITransitionContextToViewControllerKey];
// inserting presenting VC's view under presented VC's view
toVC.view.frame = [[[UIApplication sharedApplication] keyWindow] bounds];
[[context containerView] insertSubview:toVC.view belowSubview:fromVC.view];
// current frame and transform of presented VC
CGRect frame = fromVC.view.frame;
CGAffineTransform transform = fromVC.view.transform;
// determine current presented VC's view rotation and assemble
// target frame to provide naturally-looking dismissal animation
if (transform.b == -1) {
// -pi/2
frame = CGRectMake(frame.origin.x + frame.size.width, frame.origin.y, frame.size.width, frame.size.height);
} else if (transform.b == 1) {
// pi/2
frame = CGRectMake(frame.origin.x - frame.size.width, frame.origin.y, frame.size.width, frame.size.height);
} else if (transform.a == -1) {
// pi
frame = CGRectMake(frame.origin.x, frame.origin.y - frame.size.height, frame.size.width, frame.size.height);
} else {
// 0
frame = CGRectMake(frame.origin.x, frame.origin.y + frame.size.height, frame.size.width, frame.size.height);
}
UIViewAnimationOptions options = (UIViewAnimationOptionCurveEaseOut);
[UIView animateWithDuration:kDuration delay:0 options:options animations:^{
// slide view off-screen
fromVC.view.frame = frame;
} completion:^(BOOL finished) {
// required to notify the system that the transition animation is done
[context completeTransition:finished];
}];
}
#end
#implementation BTTransitioningDelegate
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
return [[BTPresentedAC alloc] init];
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
return [[BTDismissedAC alloc] init];
}
#end
Import that transitioning delegate in presenting VC:
#import "BTTransitioningDelegate.h"
Store a strong reference to an instance:
#property (nonatomic, strong) BTTransitioningDelegate *transitioningDelegate;
Instantiate in -viewDidLoad:
self.transitioningDelegate = [[BTTransitioningDelegate alloc] init];
Call when appropriate:
QLPreviewController *pc = [[QLPreviewController alloc] init];
pc.dataSource = self;
pc.delegate = self;
pc.transitioningDelegate = self.transitioningDelegate;
pc.modalPresentationStyle = UIModalPresentationFullScreen;
[self.navigationController presentViewController:pc animated:YES completion:nil];
I ended up subclassing the UINavigationController and overriding its rotation methods. The following solution works on iOS 7, but I believe there is a bug in iOS 8 beta 5 that causes the presenting view controller's view to shrink to half the screen-height after dismissing the modal in landscape orientation.
UINavigationController subclass:
- (BOOL)shouldAutorotate
{
return NO;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationPortrait;
}

iOS hiding navigation bar due to scroll amount

I've tried to implement such a table view that detects scroll amount and decides to show navigation bar or do not.
#interface HomeViewController () {
NSInteger scrollAmount;
bool navbarHidden = NO;
}
#implementation HomeViewController
#synthesize lastContentOffset = _lastContentOffset;
bool navbarHidden = NO;
- (void)awakeFromNib
{
[super awakeFromNib];
scrollAmount = 0;
distance = 50;
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGPoint offset = scrollView.contentOffset;
CGRect bounds = scrollView.bounds;
UIEdgeInsets inset = scrollView.contentInset;
if (offset.y > self.lastContentOffset.y)
{
scrollAmount++;
}
else
{
scrollAmount--;
}
bool awayFromTop = offset.y > distance + inset.top;
if (awayFromTop && !navbarHidden) {
[[self navigationController] setNavigationBarHidden:YES animated:YES];
navbarHidden = YES;
} else if (!awayFromTop || (scrollAmount < -100)) {
[[self navigationController] setNavigationBarHidden:NO animated:YES];
navbarHidden = NO;
}
self.lastContentOffset = offset;
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
scrollAmount = 0;
}
Basically, scrollViewDidScroll counts scroll amount and if user scrolls upward, it is decrementing scrollAmount by minus 1.
And if offset is close enough to top of the screen (!awayFromTop) OR scroll amount is smaller than -100, it is expected to navigation bar is hid.
When I put a NSLog for scrollAmount program runs correct, it is hiding nav. bar when user aways from top or shows when approaches to top and scrollAmount is printed correctly.
But whenever scrollAmount reaches to -100 [[self navigationController] setNavigationBarHidden:NO animated:YES]; is not executed and somehow scrollViewDidScroll is called infinitely I mean program enters an infinite loop. scrollAmount is printed like -100,-101,-102...,-1005...
Then I've used below code:
if ([scrollView.panGestureRecognizer translationInView:self.view].y < heightOfScreen/-4.0f && !navbarHidden) {
[self.navigationController setNavigationBarHidden:YES animated:YES];
navbarHidden = YES;
} else if ([scrollView.panGestureRecognizer translationInView:self.view].y > heightOfScreen/4.0f && navbarHidden) {
[self.navigationController setNavigationBarHidden:NO animated:YES];
navbarHidden = NO;
}
Obviously [scrollView.panGestureRecognizer translationInView:self.view].y gives sth. similar to scrollAmount but it works perfect, now I wonder why my implementation has been failed. Any ideas appreciated.
The code looks right but if I were you, I would use
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
to track the scroll view scrolling, because scrollViewDidScroll generates some weird behavior in my app too. Maybe you'd like to give a try.

Resources