Currently my status bar is set to hidden, but i would like to have the scrollsToTop method working. Is there a workaround to keep the status bar hidden and detecting when it is tapped?
Thank you
If the status bar is hidden, the built-in solution with setting scrollsToTop to YES will not work.
The scroll-to-top gesture is a tap on the status bar. When a user
makes this gesture, the system asks the scroll view closest to the
status bar to scroll to the top.
You have to add a UITapGestureRecognizer to the view and detect it yourself.
You have two options:
Add an invisible view of the size of the status bar and add a gesture recognizer to it.
Add it to your main view and check if the tap occurred in the rectangle where normally status bar would be.
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer
{
CGPoint p = [gestureRecognizer locationInView:self.view];
if (CGRectContainsPoint(statusBarRect, p))
{
NSLog(#"Got a tap in the status bar area");
// Scroll to the top.
[self.scrollView setContentOffset:CGPointZero animated:YES];
}
}
You might instead consider just blending the status bar with your background (ie using the UIStatusBarStyleLightContent on a white background or UIStatusBarStyleDefault on a dark background. Can't get around the low battery content, however).
Otherwise, I'd recommend creating your own UIView with alpha of zero and a UITapGestureRecognizer in place of the status bar, and implementing your own version of scrolling to top when you pick up a tapGesture.
Related
In the Snapchat app, the status bar changes alpha when a table view is dragged down to reload.
This is the normal status bar
This is what happens when the table view is dragged down
How is the status bar alpha changed? And how is the frame changed? Originally I thought it was a snapshot, but the clock changes as it normally should.
This should do the trick for the fade animation:
/* Swift 3 */
let statusBarWindow = UIApplication.shared.value(forKey: "statusBarWindow") as? UIWindow
UIView.animate(withDuration: 2) {
statusBarWindow?.alpha = 0.2
}
/* Objective-C */
UIWindow *statusBarWindow = (UIWindow *)[[UIApplication sharedApplication] valueForKey:#"statusBarWindow"];
[UIView animateWithDuration:2.f
animations:^{ [statusBarWindow setAlpha:0.2]; }
completion:nil];
You can also set statusBarWindow's frame and move it how you want. Have fun ;]
They are most likely either grabbing the window the status bar is in and animating that or they are placing a window above the status bar with a view and animating that. I don't have the app to check but it has to be either of these two methods. To place a window above the status bar I think it is window.windowLevel = UIWindowLevelStatusBar + 1 so that it will be above your status bar. If they are grabbing the reference you have to loop through the application windows to find it and I have never tried to find the Statusbar window but I have grabbed the keyboard window. If the clock is indeed being pulled down then it could be a combination of both the getting the window the status bar is in and adding a window above and animating both. Facebook grabs the keyboard window to scroll it up in Facebook Messenger. Hope this helps you.
Edit:
I found an example of getting the status bar window here.
Also I would recommend using CAAnimations and avoid really changing anything. Just use the animations to give that appearance.
I'm building an iOS 8 app that makes use of the new hidesBarsOnSwipe property on UINavgitationController to hide the nav bar while scrolling. At the same time that the nav bar hides, I'm also programmatically hiding the tab bar. On top of the tab bar, there is a text field which lets users comment on a post (much like Facebook). When the tab bar is hidden (by moving it downward and off the screen), the text field is moved down as well, so that it now sits at the bottom of the screen and so that there's no gap between the bottom of the screen and the text field.
So, things look great. But, turns out that the text field doesn't respond to touch events when it moves to the bottom of the screen. I did some digging and it appears that the reason is because the text field is outside of its superview (the view controller's view), and so touch events will not be sent to the text field.
So I think I've figured out why the issue is occurring, but I haven't yet figured out how to fix it. I've tried messing with hitTest:withEvent: and pointInside:withEvent: but didn't have any luck. Anyone have any solutions?
EDIT: Here is some code to make the question clearer (hopefully). When the nav controller's barHideOnSwipeGestureRecognizer is called, I am running the following code:
- (void)barHideSwipeGestureActivated:(UIPanGestureRecognizer*)gesture
{
[self animateTabBarUpOrDown:self.navigationController.navigationBar.frame.origin.y >= 0 completion:nil];
}
The method above is the following:
- (void)animateTabBarUpOrDown:(BOOL)up completion:(void (^)(void))completionBlock
{
if(!self.animatingTabBar && self.tabbarIsUp != up)
{
self.animatingTabBar = YES;
//to animate the tabbar up, reset the comments bottom constraint to 0 and set the tab bar frame to it's original place
//to animate the tabbar down, move its frame down by its height. set comments bottom constraint to the negative value of that height.
[UIView animateWithDuration:kTabBarAnimationDuration animations:^{
UITabBar *tabBar = self.tabBarController.tabBar;
if(up)
{
tabBar.frame = CGRectMake(tabBar.frame.origin.x, tabBar.frame.origin.y - tabBar.frame.size.height, tabBar.frame.size.width, tabBar.frame.size.height);
self.addCommentViewToBottomConstraint.constant = 0.0f;
}
else
{
tabBar.frame = CGRectMake(tabBar.frame.origin.x, tabBar.frame.origin.y + tabBar.frame.size.height, tabBar.frame.size.width, tabBar.frame.size.height);
self.addCommentViewToBottomConstraint.constant = -tabBar.frame.size.height;
}
} completion:^(BOOL finished) {
self.tabbarIsUp = up;
self.animatingTabBar = NO;
if(completionBlock)
{
completionBlock();
}
}];
}
}
Ok, finally found a solution for this one. I haphazardly messed around with changing the bounds of my view controller's view, but that was too hacky and ultimately didn't accomplish what I wanted it to.
What I ended up doing was changing my view controller's edgesForExtendedLayout property to be equal to UIRectEdgeAll which basically says that the view should take up the entire screen, and extend above top bars / below bottom bars.
I had to hack around a little bit with changing auto layout constraints on my text field so that it appeared in the right place at the right time, but overall, the solution was changing edgesForExtendedLayout to be UIRectEdgeAll - this makes the view take up the entire screen, so the text field is now still in the super view even when it animates downward, thus, allowing it to still receive touch events.
Using xcode6, and storyboarding
I have a toolbar that is at the top of my view. under the toolbar is a mapview. The map view extends under the status bar which is what I want. Basically I want the toolbar and status bar to be a little transparent ~20%, such that you can see the mapview beneath.
I have setup my toolbar's background color to be white 80% opaque. However I have no idea how to get the status bar to be exactly the same thing.
From everything I know, the status bar is 100% transparent, meaning it will show any view that is beneath it. I have tried to add a 20 point tall view above my toolbar, and I have set that to white, 80% transparent. That almost achieves the same effect that I am going for, but there is a black line between the toolbar and the status bar,
Am I even on the right track? How do I make the toolbar color and transparency match the status bar color and transparency and avoid the line?
EDIT:
clipsToBounds worked great at removing the line.
I added a button programmatically:
-(void)splitViewController:(UISplitViewController *)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem *)button forPopoverController:(UIPopoverController *) pc {
button.image = [UIImage imageNamed:#"bars"];
NSMutableArray *items = [self.toolbar.items mutableCopy];
if (!items) {
items = [NSMutableArray arrayWithObject:button];
} else {
[items insertObject:button atIndex:0];
}
[self.toolbar setItems:items];
}
And after I add that button my toolbar background turns back to white:
EDIT 2
So I just discovered the UIBarPositionTopAttached enum and that I can set my view as the toolbars delegate. I did this:
(UIBarPosition)positionForBar:(id )bar {
return UIBarPositionTopAttached;
}
And verified that it was called. I was hoping this would tell iOS that my toolbar is attached to the status bar and that my toolbar color and all would flow into the status bar. Is that not the case either?
Seems like attaching a toolbar to the status bar such that the status bar takes the backgroud of the toolbar should not be this hard, i.e. do I really have to create an extra 20 point high view to achieve this? And even if so seems like I am doing it wrong, as when I add button my toolbar transparency goes back to opaque.
The UIToolbar has a hairline shadow at the top. If you set the bounds clipping property it will hide the line.
toolbar.clipsToBounds = YES;
While clips to bounds works great, I think the right answer to this question use barPosition on toolbar.
There are a couple ways to do this:
Set your view as the delegate to the toolbar and implement this delegate method:
- (UIBarPosition)positionForBar:(id <UIBarPositioning>)bar {
return UIBarPositionTopAttached;
}
Or in a storyboard set a User Defined Runtime Attribute on the UIToolbar:
barPosition Number 3
According to the official UIScrollView documentation related to scrollsToTop:
If that scroll view has scrollsToTop set to NO, its delegate returns
NO from scrollViewShouldScrollToTop:, or the content is already at
the top, nothing happens.
So, as a result, the delegate method scrollViewShouldScrollToTop: is not fired when the scrollview is at the top when I tap the status bar. However, I'm trying to take advantage of this call to programatically make my own decision about which scrollview in the hierarchy needs to scroll.
So what is the best alternative to this? I'm trying to find a way to catch taps on the status bar more than anything. Based on what I've read it sounded like this was the best way to catch the call and handle it appropriately.
EDIT: The next best alternative I could think of was to place a clear UIView with a UITapGestureRecognizer over top of the status bar via a different UIWindow that sits on top.
Since iOS7 controller views go by default underneath statusBar and navigationBars.
You can take advantage of this, and add a TapGestureRecognizer to your VC´s main view. In the tap delegate method check whether the tapped point in view belongs to the status bar frame.
- (void)userTappedOnView:(UITapGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateEnded) {
CGPoint tapLocation = [recognizer locationInView:self.view];
CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
if (CGRectContainsPoint(statusBarFrame, point)) {
//Scroll other views to top here..
}
}
}
I have a ViewController with a navigation controller and a tab bar controller. This ViewController has 2 buttons that toggle the visibility of a scroll view and a map view. The main thing is to have these 2 buttons always show up in the same place regardless of orientation or the view that happens to be visible.
The problem I am having is that the MapView won't size properly. If I just give it a frame from self.view.bounds it goes under the navigation bar / tab bar - basically taking up the whole screen. This throws off the location of my toggle buttons.
I noticed my ScrollView does the same (using a background color and a translucent navigation bar) but the positioning of sub views on it stay within the visible area (between the navigation bar and the tab bar). So when I add my toggle buttons, they show in the correct place.
When I press the toggle buttons, I just re-assign the parent view of the buttons to the now displayed view (ScrollView or MapView). This always works on the scroll view but due to the positioning, they end up going under the navigation bar when the MapView is displayed.
I have tried creating the frame for the MapView manually but I get odd results. I use this for the frame:
CGRect mapFrame = CGRectMake(
0,
(self.navigationController.navigationBar.frame.size.height + [UIApplication sharedApplication].statusBarFrame.size.height),
self.view.frame.size.width,
(
self.view.frame.size.height
- (self.navigationController.navigationBar.frame.size.height + [UIApplication sharedApplication].statusBarFrame.size.height + self.tabBarController.tabBar.frame.size.height)
)
);
I then set the auto resizing masks
[self.mapView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
But with this, if I enter into the view controller in landscape mode - the MapView is off the screen. If I enter in portrait mode and then rotate to landscape, the top margin is off but about 10 points so it still goes under the navigation bar a bit (and has a visible margin between the tab bar and the bottom of the map view).
How can I make the MapView subviews only display within the visible region like the ScrollView does? I don't mind so much if the map itself goes under the navigation bar / tab bar
Ugh, I found the answer just a few minutes ago. I have no idea why this is the default behavior on iOS 7 but alas, there it is.
The solution is to add this to the viewDidLoad on the ViewController
[self setEdgesForExtendedLayout:UIRectEdgeNone];
[self setAutomaticallyAdjustsScrollViewInsets:NO];
Much thanks to the post here