I have a uiview at the top of the interface (below the status bar) that only the bottom part of it is shown.
Actually, I want to make the red uiview to slide down to be entirely shown by drag such as the notificationcenter in the native iOS and not just by taping a button.
What should I use to "touch and pull down" the uiview so it could be shown entirely ?
No needs to find a workaround of drag-n-drop. An UIScrollView can do it without any performance loss brought by listening on touches.
#interface PulldownView : UIScrollView
#end
#implementation PulldownView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (!self) {
return self;
}
self.pagingEnabled = YES;
self.bounces = NO;
self.showsVerticalScrollIndicator = NO;
[self setBackgroundColor:[UIColor clearColor]];
double pixelsOutside = 20;// How many pixels left outside.
self.contentSize = CGSizeMake(320, frame.size.height * 2 - pixelsOutside);
// redArea is the draggable area in red.
UIView *redArea = [[UIView alloc] initWithFrame:frame];
redArea.backgroundColor = [UIColor redColor];
[self addSubview:redArea];
return self;
}
// What this method does is to make sure that the user can only drag the view from inside the area in red.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (point.y > height)
{
// Leaving useless touches to the views under it.
return nil;
}
return [super hitTest:point withEvent:event];
}
#end
How to use:
1. Initialize an instance of PulldownView.
2. Add any content you want to display to the instance using [addSubview:].
3. Hide the area in red.
[pulldownView setContentOffset:CGPointMake(0, heightOfTheView - pixelsOutside)];
This is a simple example. You can add any features to it like adding a titled button bar on the bottom of the draggable area to implement click-n-drop, or adding some method to the interface to reposition it by the caller.
Make a subclass of UIView.
Override touchesBegan:withEvent and touchesMoved:withEvent.
In the touchesBegan perhaps make a visual change so the user knows they are touching the view.
In the touchesMoved use
[[touches anyObject] locationInView:self]
and
[[touches anyObject] previousLocationInView:self]
to calculate the difference between the current touch position and the last touch position (detect drag down or drag back up).
Then if you're custom drawing, call [self setNeedsDisplay] to tell your view to redraw in it's drawRect:(CGRect)rect method.
Note: this assumes multiple touch is not used by this view.
Refer to my answer in iPhone App: implementation of Drag and drop images in UIView
You just need to use TouchesBegin and TouchesEnded methods. In that example, I have shown how to use CGPoint, Instead of that you have to try to use setFrame or drawRect for your view.
As soon as TouchesMoved method is called you have to use setFrame or drawRect (not sure but which ever works, mostly setFrame) also take the height from CGPoint.
Related
I have created custom navigation bar and added one UIButton over it.
Please take a look at image attached.
I have UIButton with background image added as subview to Custom NavigationBar so portion above GREEN line is clickable not the portion below green line.
I have tried using UITapGestureRecognizer and also the touches methods ie. touchesBegan to UIImageView to handle the touch, but no luck.
I think this is only because my UIButton nontouchable potion is outside of NavigationBar Frame.
Is there any way to click the subview's portion which is ouside its parent view.
The parent view size is less than the Child view . That's why it is non clickable. Asper my knowledge only option you have is try to increase the parent view (Custom Navigationbar) frame size .
Yo need subclass Navigation Bar and add method hitTest:withEvent:. For example:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
CGRect extraTapRect = ...; // Put here needed rect
if (CGRectContainsPoint(extraTapRect, point)) {
for (UIView *subview in self.subviews.reverseObjectEnumerator) {
CGPoint subviewPoint = [subview convertPoint:point fromView:self];
UIView *result = [subview hitTest:subviewPoint withEvent:event];
if (result != nil) {
return result;
}
}
}
return [super hitTest:point withEvent:event];
}
I have a small 320x144viewcontroller named SubViewController.h which has a UITableView in it with 3 cells with a single section. I have made the tableView unscrollable and also put some shadow effect behind the tableView by grace of CALayer.
In another viewcontroller named as MainViewController.m i have added SubViewController.h as a subview to this MainViewController. Using UIPanGestureRecognizer i have successfully able to drag the SubViewContoller anywhere i want.
I make this subView visible with a UIBarButtonItem. And after selecting a cell in the tableView of the subview i made it disappear from main view with some animation.
Everything works fine.
But when i drag the subview and then try to select one cell i have to tap the cell twice. In first tap nothing actually happens except the cell turns blue(like it happens normally when you select a cell in tableView) but does not go Hidden. If i tap again then it will go hidden.
Without dragging the subview i can select one cell with a single touch and also the view goes hidden.
I have written the code for hiding the subview in didSelectRowAtIndexPath: method of the subview. And I have checked this method is not called when i select first time after dragging the subview.In the second tap or touch it is called though. And again if the user moves the subview again same problem occurs.
Surely some property of the subview got changed after dragging which i cant able to figure out.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
NSUInteger tapCount = [touch tapCount];
switch (tapCount) {
case 1:
[self performSelector:#selector(singleTapMethod) withObject:nil afterDelay:.4];
break;
case 2:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(singleTapMethod) object:nil];
[self performSelector:#selector(doubleTapMethod) withObject:nil afterDelay:.4];
break;
. . .
}
First when u want your subView to be shown ,that is on click of your UIBarButtonItem:
-(IBAction)buttonClick
{
//setup ur view dynamically as you like//
PSview=[[UIView alloc]initWithFrame:CGRectMake(5, 5, 310,450)];
PSview.backgroundColor=[UIColor blackColor];
PSview.alpha=0.8;
[PSview.layer setBorderColor: [[UIColor whiteColor] CGColor]];
[PSview.layer setBorderWidth: 3.0];
PSview.contentMode=UIViewContentModeScaleAspectFill;
PSview.clipsToBounds=YES;
[PSview.layer setBorderColor: [[UIColor whiteColor] CGColor]];
[PSview.layer setBorderWidth: 3.0];
[PSview addSubview:subView];
[self.view addSubview:PSview];
}
then later :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//since there are two tables in one view, you can differentiate them using if()
if(tableView==subView)
{
// ...ur code . ..
// write your code what needs to happen when you click a row of your subView.
[PSview removeFromSuperview];
}
if(tableView==mainView)
{
// write your code , what happens when user clicks row of the main table
}
}
Is it possible to add a UIView on the staus bar of size (320 x 20)? I don't want to hide the status bar, I only want to add it on top of the status bar.
You can easily accomplish this by creating your own window above the existing status bar.
Just create a simple subclass of UIWindow with the following override of initWithFrame:
#interface ACStatusBarOverlayWindow : UIWindow {
}
#end
#implementation ACStatusBarOverlayWindow
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
// Place the window on the correct level and position
self.windowLevel = UIWindowLevelStatusBar+1.0f;
self.frame = [[UIApplication sharedApplication] statusBarFrame];
// Create an image view with an image to make it look like a status bar.
UIImageView *backgroundImageView = [[UIImageView alloc] initWithFrame:self.frame];
backgroundImageView.image = [UIImage imageNamed:#"statusBarBackground.png"];
[self addSubview:backgroundImageView];
[backgroundImageView release];
// TODO: Insert subviews (labels, imageViews, etc...)
}
return self;
}
#end
You can now, for example in a view controller in your application, create an instance of your new class and make it visible.
overlayWindow = [[ACStatusBarOverlayWindow alloc] initWithFrame:CGRectZero];
overlayWindow.hidden = NO;
Be aware of messing with the window key status by using - (void)makeKeyAndVisible or similar. If you make your main window (the UIWindow in your Application Delegate) loose key status, you will encounter problems with scrolling scrollviews to top when tapping the status bar etc.
I wrote a static library mimicing Reeders status bar overlay, you can find it here: https://github.com/myell0w/MTStatusBarOverlay
It currently supports iPhone and iPad, default and opaque black status bar styles, rotation, 3 different anymation modes, history-tracking and lots of more goodies!
Feel free to use it or send me a Pull Request to enhance it!
All answers looks like working, but in iOS6.0 I have next problems:
1/ Rotations looks bad
2/ Window (status bar is kind of Window) needed rootViewController
I'm using answer from myell0w, but rotate works not good. I've just remove one extra window and using UIWindow from AppDelegate to implement status bar.
May be this solution is ok only for one UIViewController-app...
Ive implemented by the next way:
1/ In ApplicationDelegate:
self.window.windowLevel = UIWindowLevelStatusBar + 1;
self.window.backgroundColor = [UIColor clearColor];
self.window.rootViewController = _journalController;
2/ Create custom UIView and implement all that you need inside:
For an example touchable statusbar:
#interface LoadingStatusBar : UIControl
And easily create and add to your controller view:
_loadingBar = [[LoadingStatusBar alloc] initWithFrame:topFrame];
[self addSubview:_loadingBar];
3/ Some magic when add your controller view (in initWithFrame:)
CGRect mainFrame = self.bounds;
mainFrame.origin.y = 20;
self.bounds = mainFrame;
Your controller view will has 2 views - content view and status bar view. You can show status bar, or hide it when you want.
Frame of content view will be:
_contentView.frame = CGRectMake(0, 20, self.bounds.size.width, self.bounds.size.height);
4/ And one last magic here :)
To detect touches in non touchable area I've used:
-(id)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (point.y < 20) return _loadingBar;
return [super hitTest:point withEvent:event];
}
For now it works fine on iPad/iPhone and all iOS's from 4 to 6.
Just to dismiss the "You cannot do this comments"...
I don't know how but I know it is doable. The Feed reader app called Reeder does that.
As you can see from the screenshot, Reeder puts a small dot on the top right of the screen. When you tap it. The bar will fill the whole statusbar until you tap it again to make it small.
First of all, a big thank you to #Martin Alléus for providing the code for this implementation.
I'm just posting for a problem that I faced and the solution I used, as I believe others might experience the same issue.
If the App is started while an call is in place, the status bar height will be 40 pixels and this means that the custom status bar will be initialized with that height.
But if the call is ended while you are still in the app, the status bar height will remain still 40 pixels and it will look weird.
So the solution is simple: I've used the Notification center to subscribe to the status bar frame change delegate of the app and adjust the frame:
- (void)application:(UIApplication *)application didChangeStatusBarFrame:(CGRect)oldStatusBarFrame {
//an in call toggle was done
//fire notification
[[NSNotificationCenter defaultCenter] postNotificationName:kStatusBarChangedNotification object:[NSValue valueWithCGRect:oldStatusBarFrame]];
}
And in the ACStatusBarOverlayWindow we subscribe to the notification:
-(id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame]))
{
// Place the window on the correct level & position
self.windowLevel = UIWindowLevelStatusBar + 1.0f;
self.frame = [UIApplication sharedApplication].statusBarFrame;
self.backgroundColor = [UIColor blackColor];
//add notification observer for in call status bar toggling
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(statusBarChanged:) name:kStatusBarChangedNotification object:nil];
}
return self;
}
and our code to adjust the frame:
- (void)statusBarChanged:(NSNotification*)notification {
//adjust frame...
self.frame = [UIApplication sharedApplication].statusBarFrame;
//you should adjust also the other controls you added here
}
The kStatusBarChangedNotification is just a constant I've used for easy referrence, you can simply replace it with a string, or declare the constant globally.
Right now I have 2 different UIPickerView in side my UITableViewController. I only show them upon tapping of certain cells in the table. What I'm trying to do is to hide the pickers whenever I touch outside the pickers. Is there a delegate method or something similar to achieve this? I prefer to keep my controller as a UITableViewController instead of a simple UIViewController since I have a textView in one of the cells and scrolling after the keyboard shows is just a bit too much in a UIViewController.
Thanks in advance.
One of the Possible solutions is that when a particular cell is tapped and you handle picker (to present the picker), you can insert a view called as MASK View over the tableview. (with Frame as self.tableview.frame - yourPicker.frame.size.height ). Now when ever you get any click on this view you can handle it as follows
-(void)showMaskView{
if (!viewMaskView) {
CGRect viewRect = CGRectMake(0, 0, self.tableView.frame.size.width, self.tableView.frame.size.height - yourPicker.frame.size.height);
viewMaskView = [[MaskView alloc] initWithFrame:viewRect];
viewMaskView.delegate = self;
}
[self.view addSubview:viewMaskView];
[self.view bringSubviewToFront:viewMaskView];
}
-(void)removeMaskView{
if (viewMaskView) {
[viewMaskView removeFromSuperview];
}
//Remove the Picker
}
In the MaskView class you can handle the touch as follows
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
if(self.delegate && [self.delegate respondsToSelector:#selector(removeMaskView)])
[self.delegate removeMaskView];
}
you can see the colored mask view over the Picker in the image. When tapped it removes picker.
I have a view with multiple subviews. When a user taps a subview, the subview expands in size to cover most of the screen, but some of the other subviews are still visible underneath.
I want my app to ignore touches on the other subviews when one of the subviews is "expanded" like this. Is there a simple way to achieve this? I can write code to handle this, but I was hoping there's a simpler built-in way.
Hope this help...
[[yourSuperView subviews]
makeObjectsPerformSelector:#selector(setUserInteractionEnabled:)
withObject:[NSNumber numberWithBool:FALSE]];
which will disable userInteraction of a view's immediate subviews..Then give userInteraction to the only view you wanted
yourTouchableView.setUserInteraction = TRUE;
EDIT:
It seems in iOS disabling userInteraction on a parent view doesn't disable userInteraction on its childs.. So the code above (I mean the one with makeObjectsPerformSelector:)will only work to disable userInteraction of a parent's immediate subviews..
See user madewulf's answer which recursively get all subviews and disable user interaction of all of them. Or if you need to disable userInteraction of this view in many places in the project, You can categorize UIView to add that feature.. Something like this will do..
#interface UIView (UserInteractionFeatures)
-(void)setRecursiveUserInteraction:(BOOL)value;
#end
#implementation UIView(UserInteractionFeatures)
-(void)setRecursiveUserInteraction:(BOOL)value{
self.userInteractionEnabled = value;
for (UIView *view in [self subviews]) {
[view setRecursiveUserInteraction:value];
}
}
#end
Now you can call
[yourSuperView setRecursiveUserInteraction:NO];
Also user #lxt's suggestion of adding an invisible view on top of all view's is one other way of doing it..
There are a couple of ways of doing this. You could iterate through all your other subviews and set userInteractionEnabled = NO, but this is less than ideal if you have lots of other views (you would, after all, have to subsequently renable them all).
The way I do this is to create an invisible UIView that's the size of the entire screen that 'blocks' all the touches from going to the other views. Sometimes this is literally invisible, other times I may set it to black with an alpha value of 0.3 or so.
When you expand your main subview to fill the screen you can add this 'blocking' UIView behind it (using insertSubview: belowSubview:). When you minimize your expanded subview you can remove the invisible UIView from your hierarchy.
So not quite built-in, but I think the simplest approach. Not sure if that was what you were thinking of already, hopefully it was of some help.
Beware of the code given as solution here by Krishnabhadra:
[[yourSuperView subviews]makeObjectsPerformSelector:#selector(setUserInteractionEnabled:) withObject:[NSNumber numberWithBool:FALSE]];
This will not work in all cases because [yourSuperView subviews] only gives the direct subviews of the superview. To make it work, you will have to iterate recursively on all subviews:
-(void) disableRecursivelyAllSubviews:(UIView *) theView
{
theView.userInteractionEnabled = NO;
for(UIView* subview in [theView subviews])
{
[self disableRecursivelyAllSubviews:subview];
}
}
-(void) disableAllSubviewsOf:(UIView *) theView
{
for(UIView* subview in [theView subviews])
{
[self disableRecursivelyAllSubviews:subview];
}
}
Now a call to disableAllSubviewsOf will do what you wanted to do.
If you have a deep stack of views, the solution by lxt is probably better.
I would do this by putting a custom transparent button with the same frame as the superView. And then on top of that button I would put view that should accept user touches.
Button will swallow all touches and views behind it wouldn't receive any touch events, but view on top of the button will receive touches normally.
Something like this:
- (void)disableTouchesOnView:(UIView *)view {
UIButton *ghostButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, view.frame.size.width, view.frame.size.height)];
[ghostButton setBackgroundColor:[UIColor clearColor]];
ghostButton.tag = 42; // Any random number. Use #define to avoid putting numbers in code.
[view addSubview:ghostButton];
}
And a method for enabling the parentView.
- (void)enableTouchesOnView:(UIView *)view {
[[view viewWithTag:42] removeFromSuperview];
}
So, to disable all views in the parentViev behind yourView, I would do this:
YourView *yourView = [[YourView alloc] initWithCustomInitializer];
// It is important to disable touches on the parent view before adding the top most view.
[self disableTouchesOnView:parentView];
[parentView addSubview:yourView];
Just parentView.UserInteractionEnabled = NO will do the work.
Parent view will disable user interaction on all the view's subviews. But enable it does not enable all subviews(by default UIImageView is not interactable). So an easy way is find the parent view and use the code above, and there is no need to iterate all subviews to perform a selector.
Add a TapGestureRecognizer to your "background view" (the translucent one which "grays out" your normal interface) and set it to "Cancels Touches In View", without adding an action.
let captureTaps = UITapGestureRecognizer()
captureTaps.cancelsTouchesInView = true
dimmedOverlay?.addGestureRecognizer(captureTaps)
I will give my 2 cents to this problem.
Iteratively run userInteractionEnabled = false it's one way.
Another way will be add a UIView like following.
EZEventEater.h
#import <UIKit/UIKit.h>
#interface EZEventEater : UIView
#end
EZEventEater.m
#import "EZEventEater.h"
#implementation EZEventEater
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
self.userInteractionEnabled = false;
}
return self;
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//EZDEBUG(#"eater touched");
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
}
In your code you add the EZEventEater view to cover all the views that your may block the touch event.
Whenever you want to block the touch event to those views, simply call
eater.userInteractionEnabled = YES;
Hope this helpful.
In Swift 5, I achieved this behaviour by placing a view right on top(the highlighted one) and setting:
myView.isUserInteractionEnabled = true
This does not let the touches go through it, thus ignoring the taps.
For my app, I think it will be sufficient to disable navigation to other tabs of the app (for a limited duration, while I'm doing some processing):
self.tabBarController.view.userInteractionEnabled = NO;
Also, I disabled the current view controller--
self.view.userInteractionEnabled = NO;
(And, by the way, the recursive solutions proposed here had odd effects in my app. The disable seems to work fine, but the re-enable has odd effects-- some of the UI was not renabled).
Simple solution. Add a dummy gesture that does nothing. Make it reusable by adding it to an extension like this:
extension UIView {
func addNullGesture() {
let gesture = UITapGestureRecognizer(target: self,
action: #selector(nullGesture))
addGestureRecognizer(gesture)
}
#objc private func nullGesture() {}
}
setUserInteractionEnabled = NO on the view you want to disable
I had the same problem, but the above solutions did not help.
I then noticed that calling
super.touchesBegan(...) was the problem.
After removing this the event was only handled by the top-most view.
I hope this is of help to anybody.