UIView 'falling rain' transparent overlay that can be clicked through? - ios

I'd like to create a UIView which will animate falling rain, which should be above all other views, at the very front, but transparent, and it shouldn't register taps or interaction at all, so tapping the UI behind it should behave exactly as it does now, before I've implemented it. It'd act as an overlay, and nothing more.
Is this as simple as setting UserInteractionEnabled to NO, or is there more to it? Do I need to subclass UIView, or override something, etc.?

Yes, set userInteractionEnabled = NO; No need to subclass UIView. e.g.:
UIView *overlay = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
overlay.backgroundColor = [UIColor colorWithWhite:0 alpha:.5];
[self.view addSubview:overlay];

You can make use of hittest after adding the transparent subview as #zenith has explained.
The implementation of hitTest:withEvent: in UIResponder does the following:
It calls pointInside:withEvent: of self
If the return is NO, hitTest:withEvent: returns nil. the end of the story.
If the return is YES, it sends hitTest:withEvent: messages to its subviews. it starts from the top-level subview, and continues to other views until a subview returns a non-nil object, or all subviews receive the message.
If a subview returns a non-nil object in the first time, the first hitTest:withEvent: returns that object. the end of the story.
If no subview returns a non-nil object, the first hitTest:withEvent: returns self
This process repeats recursively, so normally the leaf view of the view hierarchy is returned eventually.
However, you might override hitTest:withEvent to do something differently. In many cases, overriding pointInside:withEvent: is simpler and still provides enough options to tweak event handling in your application.

Related

Where and when to initialize/create view programatically

I had this question when/where to create and initialize views that are created programatically, so I hope some discussions here will shed more light on this topic for me.
This slide:
says: "not to initialize something based on the geometry of the view in viewDidLoad" and suggests viewDidAppear.
Imagine my view controller has view. I want to add 10 dynamic UIButtons to it.
Shall I put the code like below to the viewDidAppear?
-(void) viewDidAppear:(BOOL)animated
{
...
UIButton *button1 = [[UIButton alloc] initWithFrame: rect1];
[self.view addSubview: button1];
UIButton *button2 = [[UIButton alloc] initWithFrame: rect2];
[self.view addSubview: button2];
...
}
But this creates the buttons each time the view is shown. Is it what we want?
On the other hand if I put the code in viewDidLoad slide suggest not to initialize geometry of these views there.
Or shall we create buttons in viewDidLoad and set their frames in viewDidAppear?
What approach do you usually take?
But this creates the buttons each time the view is shown. It's true.
So the best thing you can do is to add a boolean (lets name it isLaunched). You set it to FALSE in the method -(void)viewDidLoad
Then add a if condition in your -(void)viewDidAppear where you perform creation of buttons (or other stuff) and set the boolean to true at the end.
You should have something like that :
-(void)viewDidLoad
{
//some settings
isLaunched = FALSE;
}
-(void)viewDidAppear:(BOOL)animated
{
if(!isLaunched)
{
//creating and adding buttons
isLaunched = TRUE;
}
}
zbMax (and now Amar) offered good solutions to implement the view creations in viewDidAppear: I will provide the rational for doing this (over viewDidLoad).
It is pretty simple actually. In viewDidLoad none of the views are actually setup yet, so any attempt to set/create frames or bounds will be extremely inconsistent. Struts and springs (or autolayout) will take effect after this method which will create additional changes to your views. viewDidAppear: is the correct place to do this because you can now rely on existing views and setting frames.
Reason for not playing with the geometry in viewDidLoad is because view is still in the memory and not on the window. Once the view is put on the window, then you can specify geometry. That happens when viewDidAppear is called for your controller.
As recommended, you should do all the initialisation in viewDidLoad as this is one time task and need not be repeated. Hold references to the added subviews and give them appropriate frame in viewDidAppear.
When you are dealing with custom UIView and its subviews, layoutSubviews is the method you need to override in the custom view in order to rearrange the geometry of its subviews.
Hope that helps!

Can I add a subview inside a subview?

I have added a view inside ViewController using [self.view addSubview:newView]. newView is a UIView type object.
Inside newView I added added a few views using [self addSubview:polyg];.
Polygs also inherit from UIView. But they never get drawn. drawRect:(CGRect)rect is never called.
I'm new to iOS and have no idea what I'm doing wrong.
It probably has something to do with the fact that I did not use self.view when adding views to newView. I can't even use self.view inside newView.
how can I make this work?
edit:
When i create these polyg objects I'm using initWithFrame and sending newView's frame, like this: [[Polyg alloc] initWithFrame:self.frame];
Could this be the cause? Polyg is going to be a movable and resizable polygon, so I figured each should be able to draw on the entire screen.
edit:
I created a method inside newView called touchX:Y that is called from the viewController whenever I touch my finger on the screen. Inside I wrote this: NSLog(#"subview = %i", self.subviews.count);. Whenever I touch the screen I see this on the console: subview = 89 so I'm pretty sure the subviews were added to newView.
I tried adding this to the same touchX:Y method:
for (UIView* subview in self.subviews) {
[subview setNeedsDisplay];
}
But the subviews' drawRect is never called.
The most likely possibility is that newView is either completely off the screen or have a size of (0,0). Right before you add the ployg subviews, use the debugger to print out the frame of newView.

Can I create a totally transparent UIView that receives touches?

I want to create a totally transparent UIView overlay (and it has subviews) to receives touches. I could set the alpha to a low value (like 0.02) to get an approximate effect.
But I wonder is it possible for a alpha == 0 UIView to receives touches, through other UIView configs?
You can accomplish this by overriding the hitTest:withEvent: method in the class of your fully transparent view, like:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return self;
}
The implementation of hitTest:withEvent: doesn't have to be that simple, of course. The point is that you can cause even a fully transparent view to be touchable as long as something returns that view from hitTest:withEvent:.
Do note, however, that screwing around with hitTest:withEvent: is an easy way to create some very weird bugs. Use this method with caution.
The better way to do this is to set the background colour:
UIView *view = ...;
view.backgroundColor = [UIColor clearColor];
Add a UITapGestureRecognizer to this to hook up a selector to respond to tapping.

Pattern to fill the entire screen

Every screen of my app has a common tint. Its not a background. Its a pattern that fills the entire screen and it is top of all the views. You can see the pattern flow continuously from one view to another inside the same screen. And it neither obscures other elements nor participate in event handling.
I tried implementing it with this code in my ViewController.
UIColor* texture = [UIColor colorWithPatternImage:[UIImage imageNamed:#"Texture.png"]];
UIView* tintView = [[UIView alloc] initWithFrame:self.view.bounds];
[tintView setBackgroundColor:texture];
[tintView setAlpha:0.5];
[self.view addSubview:tintView];
But it doesn't pass on touches to the views behind it.
tintView shouldn't participate in any event handling. Rather it should let other elements behind it, handle the events like they do it normally.
Other way of doing it is set this as a background of the view property of a UIViewController and set a common alpha for all other subviews of view to show the pattern behind. That will be redundant in most ways.
Any better way of doing this?
Make your tintView a subclass of UIView and implement the hitTest:withEvent: method, returning nil. This will make your view transparent to touches. Or set userInteractionEnabled to NO.
Set the background color with a Textured image
UIImage *bgimg = [UIImage imageNamed:#"Texture.png"];
self.view.backgroundColor = [UIColor colorWithPatternImage:bgimg];

Why on iPad, self.clearsContextBeforeDrawing = NO; won't make the drawRect not clear itself before drawing

I have a TestView inherited from UIView, and everything is drawn on the screen using its drawRect method.
But if I don't want drawRect to clear the view before drawing, I used
self.clearsContextBeforeDrawing = NO;
self.opaque = NO; // also added because the doc says the rect
// passed to drawRect will be filled with
// background color first if it is set to YES
in the initWithFrame method.
The drawRect method is invoked by using
[self.view setNeedsDisplay];
in the ViewController event handler. (for touchesMoved events)
But still, everything is cleared before anything is drawn? How to make it work?
I think this answers your question. From the answer:
You cannot prevent the contents from being erased by doing the following:
[self setClearsContextBeforeDrawing: NO];
This is merely a hint to the graphics engine that there is no point in having it pre-clear the view for you, since you will likely need to re-draw the whole area anyway. It may prevent your view from being automatically erased, but you cannot depend on it.

Resources