I have my app on iPhone with a UIViewController with some stuff inside.. image, textbox, etc...
is there a way to draw a line or something like this using opengl directly inside the UIViewController?
thanks in advance
Are you sure about the OpenGL?
I believe it will be impossible to use OpenGL above regular views.
You might add a custom view (inherit from UIView) above all the other views and draw anything you want on that view.
This view should have a transparent background color.
In addition, if you want to interact with the other views (tap buttons, edit text views, scroll etc.) then you will have to implement the hitTest method in your custom view in order to pass the touch event to the views that are located under this one...
EDIT: Don't mess with the hitTest. Just uncheck the User Interaction Enabled in the XIB...
EDIT: Code sample:
#interface TransparentDrawingView : UIView {
CGPoint fromPoint;
CGPoint toPoint;
}
- (void)drawLineFrom:(CGPoint)from to:(CGPoint)to;
#end
#implementation TransparentDrawingView
- (void)initObject {
// Initialization code
[super setBackgroundColor:[UIColor clearColor]];
}
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
[self initObject];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aCoder {
if (self = [super initWithCoder:aCoder]) {
// Initialization code
[self initObject];
}
return self;
}
- (void)drawRect:(CGRect)rect {
// Drawing code
// Draw a line from 'fromPoint' to 'toPoint'
}
- (void)drawLineFrom:(CGPoint)from to:(CGPoint)to {
fromPoint = from;
toPoint = to;
// Refresh
[self setNeedsDisplay];
}
#end
First I think you should know responsibilities of UIView and UIViewController.
UIView is mainly responsible for drawing (override touchBegin, ToucheMove, etc.), animating, manage subviews, and handle event; UIViewController is mainly responsible for loading, unloading views etc.
So, you should 'draw' this line at your customized view (UIView), not view controller.
Second: If you only need to display some simple shapes or lines. I suggest you use UI controls and images. Even 'drawRect' is not recommended since it will cause more resources used. Surely OpenGL need much resources.
The answer is quite simple, you have to create a new class extending UIView, and override the drawRect function in order to draw (with Quartz...) your line. Use this method if you want gradient background as well.
I am also developping with Adobe Flex 4, and I notice that iPhone SDK has no Skin and Layout "patterns" like Flex 4 does (for instance, UIView could have a Skin (in a separate file..) and a Layout (horizontal, Vertical, Tile....)), for me that is a terrible lack!
Its something that the open source library Three20 tries to bring to the iPhone SDK.
Related
I'm in a dilemma which method to use for setting frames of custom UIViews with many subviews in it and still have animations and automatically adjust to rotations. What I usually do when I create a new viewcontroller is alloc my custom view in loadView or viewDidLoad, e.g:
-(void)viewDidLoad
{
[super viewDidLoad];
detailView = [[DetailView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
detailView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
self.view = detailView;
}
Normally this width & height is not correct for an iPhone5-screen (the actual view-frame is not set until viewWillAppear) but because of the autoresizingmask it all works out.
Then in the initWithFrame of the custom UIView DetailView, I alloc all subviews with CGRectZero, e.g:
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
label = [[UILabel alloc] initWithFrame:CGRectZero];
[self addSubview:label];
}
}
Then I override layoutsubviews to set all frames of all subviews. This works perfectly for any screen size and any orientation, e.g:
-(void)layoutSubviews
{
[super layoutSubviews];
label.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
}
However, I just found out that layoutSubviews is not so great when you use animations, because when you use animations in an animationblock, layoutsubviews is called in the middle of the animation and it completely breaks the animation, e.g:
-(void)animateLabel
{
[UIView animateWithDuration:0.4f animations:^
{
label.transform = CGAffineTransformMakeTranslation(100, 100);
}];
}
I believe there are ugly workarounds this by using flags for each animation and in layoutsubviews use those flags to set the correct start or endframe of the animated block but I don't think I should have to create a flag for each animation I want to do.
So my problem is now: how am I supposed to have a custom UIView WITH animations that also automatically adjusts itself to rotations?
The only solution I can come up with right now (that I don't like):
Don't use layoutSubviews but use the setFrame/setBounds method of the custom UIView to set the frames of all subviews. Then check in the viewController every time a rotation occurs and then use the setFrame/setBounds method of the custom UIView to change all frames of all subviews. I don't like this solution because the rotation methods are different in iOS5 and iOS6 and I don't want to have to do this in every UIViewController with it's own custom UIView.
Any suggestions?
I have recently started overriding viewDidLayoutSubviews (many times instead of viewWillAppear) in my UIViewControllers.
Yes viewDidLayoutSubviews is called on rotations. (from comment)
The method fires after all the internal layouts have already been completed so all finalized frames should be setup, but still give you the time you need to make adjustments before the the view is visible and shouldn't have any issues with animations because you are not already inside an animation block.
viewcontroller.m
- (void)viewDidLayoutSubViews {
// At this point I know if an animation is appropriate or not.
if (self.shouldRunAnimation)
[self.fuView runPrettyAnimations];
}
fuView.m
- (void)runPrettyAnimations {
// My animation blocks for whatever layout I'd like.
}
- (void)layoutSubviews {
// My animations are not here, but non animated layout changes are.
// - Here we have no idea if our view is visible to the user or may appear/disappear
// partway through an animation.
// - This also might get called far more than we intend since it gets called on
// any frame updates.
}
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.
I have a custom UIView that I'm using for the UITableView header. I'm creating it in code. (I'm basically following the TVAnimationGestures from WWDC 2010). For UI elements that are some offset from the left hand side, it looks good in either orientation. However, if I want a UILabel that is offset from the right hand size, I'm not sure what to do. If I were in IB, I would use the springs/struts. But not sure how to do that in code. Thanks.
springs/struts in IB is a GUI interface to autoresizingMask:
http://developer.apple.com/library/ios/#documentation/uikit/reference/uiview_class/UIView/UIView.html#//apple_ref/doc/uid/TP40006816-CH3-SW6
For right justification, You want to position the label with the correct offset when you first create it and set autoresizingMask to UIViewAutoresizingFlexibleLeftMargin (and any other that you want.)
A simple way to do this is to implement a method in the view which will place it's subviews according to the UIInterfaceOrientation.
You can then call this method from the UIViewController when the rotation is detected.
For example:
In YourView.h
#interface YourView : UIView {
}
// Public API
- (void)placeWidgetsForInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
#end
In YourView.m
#import "YourView.h"
#implementation YourView
...
#pragma mark -
#pragma mark Public API
- (void)placeWidgetsForInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Change frames of subviews according to orientation in here
}
#end
Then in YourController.m
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
YourView *theView = (YourView *) self.view; // Or where ever your view is
[theView placeWidgetsForInterfaceOrientation:toInterfaceOrientation];
}
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.
I have subclassed UIImageView and tried to override drawRect so I could draw on top of the image using Quartz 2D. I know this is a dumb newbie question, but I'm not seeing what I did wrong. Here's the interface:
#import <UIKit/UIKit.h>
#interface UIImageViewCustom : UIImageView {
}
- (void)drawRect:(CGRect)rect;
#end
And the implementation:
#import "UIImageViewCustom.h"
#implementation UIImageViewCustom
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
}
return self;
}
- (void)drawRect:(CGRect)rect {
// do stuff
}
- (void)dealloc {
[super dealloc];
}
#end
I set a breakpoint on drawRect and it never hits, leading me to think it never gets called at all. Isn't it supposed to be called when the view first loads? Have I incorrectly overridden it?
It'll only get called if the view is visible, and dirty. Maybe the problem is in the code that creates the view, or in your Nib, if that's how you're creating it?
You'll also sometimes see breakpoints failing to get set properly if you're trying to debug a "Release" build.
I somehow missed the first time that you're subclassing UIImageView. From the docs:
Special Considerations
The UIImageView class is optimized to
draw its images to the display.
UIImageView will not call drawRect: in a
subclass. If your subclass needs
custom drawing code, it is recommended
you use UIView as the base class.
So there you have it. Given how easy it is to draw an image into your view using [UIImage drawInRect:], or by using CALayer, there's probably no good reason to subclass UIImageView anyway.
Try to add
Edit:
- (id)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
[self setClearsContextBeforeDrawing:YES];//add this line also
}
return self;
}
- (void)setNeedsDisplay{
[self setNeedsDisplayInRect:self.frame];
}
into your code.
hope this helps.
Thanks,
madhup
Not directly answering your question, but may solve your problem:
Why do this with a subclass of UIImageView? Subclassing can always be problematic, especially for classes that aren't designed to be subclassed--and I bet UIImageView isn't. If you just want to draw stuff on top of an image, then create a view with a transparent background, draw whatever you want, and place it directly over the image.
Depending on your specific case, it could make sense to use a UIButton with a background image instead of a UIImageView. You could even set userInteractionEnabled to NO and the result would be indistinguishable from a UIImageView.
In this case your UIButton subclass would simply include:
- (void)drawRect:(CGRect)rect
{
if (kSomethingSpecial) {
[self setBackgroundImage:[UIImage imageNamed:#"RedBackground.png"]
forState:UIControlStateNormal];
}
}
Disclaimer - I would only recommend this if you have plans to use the image as a button at least some of the time. I can't wholeheartedly endorse using a UIButton as a workaround for this drawRect behaviour in UIImageView, as I'm sure Apple has its reasons for writing their API this way, like Mark referenced.