How to update/redraw UIView - ios

I have a UIView, in the view's drawRect method I am using CGRect to draw lines and UILabel to draw a label onto the view.
I would like to change the color of the lines once the view is touched. When the view is touched, it notifies my ViewController that there was a touch:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.controller viewTouched:self];
}
My ViewController responds by removing the view from the superview, creating a new UIView, and setting any necessary properties, including a new color for the lines, and re-adding the view to the superview:
-(void)redrawView:(SomeView *)view asSelected:(BOOL)selected
{
[view removeFromSuperview];
SomeView *newView = [[SomeView alloc] initWithFrame:view.frame];
newView.tintColor = (selected) ? [UIColor blueColor] : [UIColor redColor];
newView.controller = self;
[self.scrollView addSubview:newView];
}
The problem I am having is the UILabel never gets removed from the UIView. It keeps drawing a new UILabel on top of the old one(s).
Am I redrawing the view correctly? Am I redrawing the view in the most efficient manner? What is it that I do not understand about redrawing views?
Your help is much appreciated.

Try to place redrawView method code inside the drawRect in your UIView class.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.controller viewTouched:self]; // ask to refresh your view inside viewTouched
// by calling [self setNeedsDisplay:YES]; there
}

Related

Hit Testing a UIButton located outside bounds of SuperView

I am trying to animate this view ControlsView up by touchUpInside in the UIButton which is the carrot character inside the white square in the image attached. When the button is hit, a delegate method is fired an the controlsView is animated up. The problem is that because the UIButton is outside of the bounds of controlsView it does not receive touch info.
I have thought about this a lot and read up on some potential candidate solutions. Such as detecting the touch event on a super view by overriding hitTest:withEvent:. However, the UIButton is actually a subview of CockPitView which is a subview of ControlsView which is a subview of MainView. MainView, it seems, is the rectangle whose coordinates the UIButton would truly lie in. So would I override hitTest there and pass the touch info to CockPitView, where I could then have my Button trigger its action callback?
Has anyone encountered this or a similar problem and have any advice on how to proceed?
You should override hitTest in your custom view CockPitView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (!self.clipsToBounds && !self.hidden && self.alpha > 0) {
for (UIView *subview in self.subviews.reverseObjectEnumerator) {
CGPoint subPoint = [subview convertPoint:point fromView:self];
UIView *result = [subview hitTest:subPoint withEvent:event];
if (result != nil) {
return result;
}
}
}
return nil;
}
CockPitView has to be a subclass of UIView
You may want to use -pointInside:withEvent:. In your button's superview, i.e., ControlsView, override the method like this:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
// self.button is the button outside the bounds of your ControlsView
if (CGRectContainsPoint(self.button.bounds, [self convertPoint:point toView:self.button])) {
return YES;
}
return [super pointInside:point withEvent:event];
}
By doing this, your ControlsView claims that the points inside the bounds of your button should be treated like the points inside your ControlsView.

UIView blocking UIScrollView's gesture recognizer

Hi I have a UIView on top of a UIScrollView like this. The UIView is a container that is itself transparent but has many subviews which are not.
-------------------------
- UIScrollView -
- -
- ------------- -
- - UIView - -
- ------------- -
-------------------------
The UIView is transparent but it prevents the UIScrollView from scrolling when touched. Some of the subviews are buttons so they have tap gestures that override the scrolling action but the transparent gaps in the frame of the UIView still blocks the scrolling gesture. Is there a way to prevent this from happening? I would still like to use it as a container to hold my other subviews.
You should not set userInteractionEnabled to NO hence you want it intractable. I think the right way is to subclass a UIView and override - pointInside: withEvent: like below.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGPoint localPoint = [self convertPoint:point fromView:self];
for (UIView *subview in self.subviews) {
if ([subview pointInside:localPoint withEvent:event]) {
return YES;
}
}
return NO;
}
Use this view you can interact with subviews inside the view and scroll scroll view if not touch on subviews.
Thanks for the answers guys. But I found this solution which worked well for me:
How to get touches when parent view has userInteractionEnabled set to NO in iOS
Set userInteractionEnabled to FALSE on your view inside the scroll view.
myView.userInteractionEnabled = FALSE;

Touch events on UITableView's backgroundView

I've got a UITableViewController, and the tableView has a view as backgroundView (even more, it's a MKMapView), set with
self.tableView.backgroundView = _mapView;
Currently the background view is showing on the screen (I also have a transparent table header to achieve this). I'm not able to make the mapView (tableView's backgroundView) respond to user interaction. My table header, which is above the map view, is a subclass of UIView with the following override:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
LogDebug(#"Clic en transparent view");
if (_transparent)
{
return nil;
}
return [super hitTest:point withEvent:event];
}
so it's supposed to pass through the events; I don't know what's wrong, and didn't find any solution in other similar questions.
I must keep the mapView as the tableView's background property, so please take this into account.
Any idea?
it's relatively late to answer the question. But I ran into this problem on my own project, and after some research I got the answer. First off, I didn't assign the view in the back to tableView.backgroundView. Instead, I made the tableView.backgroundColor transparent then put another view under the table view.
EDIT:
by "under the table" I mean the tableView and the anotherView are children to the same parent view and tableView.zPosition is greater than anotherView.zPosition.
Then all I did is override the tableView:
(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
and let the tableView avoid holding the events that are not in its cells.
Here's the code:
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
/// the height of the table itself.
float height = self.bounds.size.height + self.contentOffset.y;
/// the bounds of the table.
/// it's strange that the origin of the table view is actually the top-left of the table.
CGRect tableBounds = CGRectMake(0, 0, self.bounds.size.width, height);
return CGRectContainsPoint(tableBounds, point);
}
it works like a charm for me. hope it'll help any other people who run into this problem.
I guess that the touchEvents cant be passed to tableView's backgroundView.
So, what i did is ,
For headerView which is a subclass of UIVIEW, i added a class property as mapView.
In HeaderView.h
#property (nonatomic,assign) UIView *mapView;
And, in tableViewController
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
HeaderView *view =[[TestView alloc] initWithFrame:CGRectMake(0, 0, 200, 100)];
view.mapView =tableView.backgroundView;
return view;
}
And override hitTest in HeaderView and return this mapView.
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
return self.mapView;
}
Now the mapView receives that touchEvent and responds accordingly..Add extra code that you might interest.
Hope this helps.
Santhu answer almost worked for me, I passed hit test to mapView instead of just returning it, also I needed to made a point check because I wanted only header touch events passing through so there is my version of hit test on the HeaderView:
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
if(point.y > self.frame.size.height){
return nil;
}
return [self.mapView hitTest:point withEvent:event];
}
Put this in a UITableView subclass, the downside is you can no longer tap cells but it hopefully is a pointer in the right direction.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{}
FYI, this is an old post, and UITableView.backgroundView does receive touches now. So, if your content in your backgroundView isn't receiving touches it's due to some other issue.

UIGestureRecognizer subView recognizer problem

The title is hard .
The the main case is like this
UIView *superView = [[UIView alloc] initWithFrame:CGRectMake(0,0,400,400)];
UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(-200,-200,400,400)];
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapAction:)];
[subView addGestureRecognizer:tapGesture];
[superView addSubView:subView];
OK , you will find that the tap gesture will take effect when you click the area in (0,0,200,200) , if you click the point (-150,-150) the tap gesture will not take effect.
I don't know whether the click outside the superView bounds to cause this problem or not.
Anyone have any idea how to fix this?
To allow subviews lying outside of the superview to respond to touch, override hitTest:withEvent: of the superview.
Documentation on Event Delivery
Touch events. The window object uses hit-testing and the responder chain to find the view to receive the touch event. In hit-testing, a window calls hitTest:withEvent: on the top-most view of the view hierarchy; this method proceeds by recursively calling pointInside:withEvent: on each view in the view hierarchy that returns YES, proceeding down the hierarchy until it finds the subview within whose bounds the touch took place. That view becomes the hit-test view.
Create a subclass of UIView.
Override hitTest:withEvent.
Use this UIView subclass for the superview.
Add method below in subclass:
(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
NSEnumerator *reverseE = [self.subviews reverseObjectEnumerator];
UIView *iSubView;
while ((iSubView = [reverseE nextObject])) {
UIView *viewWasHit = [iSubView hitTest:[self convertPoint:point toView:iSubView] withEvent:event];
if(viewWasHit) {
return viewWasHit;
}
}
return [super hitTest:point withEvent:event];
}
Note: Reverse enumerator used since subviews are ordered from back to front and we want to test the front most view first.
The only workaround I've found for case like that is to create an instance of a view that is transparent for touches as main view. In such case inner view will respond to touches as it fits bounds of main. In the class I've made from different examples found in the net I can control the level of "touch visibility" like so:
fully visible - all of the touches end up in the view.
only subviews - the view itself invisible, but subviews get their touches.
fully invisible - pretty self explanatory I think :)
I didn't try to use it with gesture recognizers, but I don't think there will be any problem, as it works perfectly with regular touches.
The code is simple...
TransparentTouchView.h
#import <UIKit/UIKit.h>
typedef enum{
TransparencyTypeNone = 0, //act like usual uiview
TransparencyTypeContent, //only content get touches
TransparencyTypeFull //fully transparent for touches
}TransparencyType;
#interface TransparentTouchView : UIView {
TransparencyType _transparencyType;
}
#property(nonatomic,assign)TransparencyType transparencyType;
#end
TransparentTouchView.m
#import "TransparentTouchView.h"
#implementation TransparentTouchView
#synthesize
transparencyType = _transparencyType;
- (id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
// UIView will be "transparent" for touch events if we return NO
switch (_transparencyType) {
case TransparencyTypeContent:
for(UIView* subview in self.subviews){
CGPoint p = [subview convertPoint:point fromView:self];
if([subview pointInside:p withEvent:event]){
return YES;
}
}
return NO;
break;
case TransparencyTypeFull:
return NO;
default:
break;
}
return YES;
}
#end
I believe that you can accomodate it to your needs.

Touch Event on UIView

Does anyone have any sample code for detecting touches on a dynamically created UIView? I have found some references to touchesBegan but cannot figure out how to implement it...
A very general way to get touches is to override these methods in a custom UIView subclass:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
The official docs for these methods is under Responding to Touch Events in the UIResponder class docs (UIView inherits those methods since it's a subclass of UIResponder). Longer and more introductory docs can be found in the Event Handling Guide for iOS.
If you just want to detect a tap (touch-down, then touch-up within your view), it's easiest to add a UIButton as a subview of your view, and add your own custom method as a target/action pair for that button. Custom buttons are invisible by default, so it wouldn't affect the look of your view.
If you're looking for more advanced interactions, it's also good to know about the UIGestureRecognizer class.
Create touch event with UIAnimation on UIView , you can manage touches on UIView anywhere .
Following Code : here self.finalScore is a UIView , and cup is a UIImageView . I handle
the touch event on UIImageView , it present inside the UIView .
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch1 = [touches anyObject];
CGPoint touchLocation = [touch1 locationInView:self.finalScore];
CGRect startRect = [[[cup layer] presentationLayer] frame];
CGRectContainsPoint(startRect, touchLocation);
[UIView animateWithDuration:0.7
delay:0.0
options:UIViewAnimationCurveEaseOut
animations:^{cup.transform = CGAffineTransformMakeScale(1.25, 0.75);}
completion:^(BOOL finished) {
[UIView animateWithDuration:2.0
delay:2.0
options:0
animations:^{cup.alpha = 0.0;}
completion:^(BOOL finished) {
[cup removeFromSuperview];
cup = nil;}];
}];
}
Like UITapGestureRecognizer is another way of handle the touch events on UIView ....
UITapGestureRecognizer *touchOnView = [[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(releaseButAction)] autorelease];
// Set required taps and number of touches
[touchOnView setNumberOfTapsRequired:1];
[touchOnView setNumberOfTouchesRequired:1];
// Add the gesture to the view
[[self view] addGestureRecognizer:touchOnView];
Create your own custom UIView from a subclass you created by inheriting UIView and override the following method inside the sub-class,
(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
}
With gesture recognizer, it is much easy to catch touch events.
Find one guested from component library in storyboard:
For example, drag Tap Gesture Recognizer to your view. It is not shown in the view UI. You can find it from Document outline in the left panel in Storyboard:
The final step is to link it (by control-drag) to the view as delegate where touch is monitored, and control-drag to your view controller class as an action. Here is the picture of link connections.
and action code:
#IBAction func tapAction(_ sender: Any) {
// touch is captured here.
}
Instead of capturing touch using gesture, I would suggest to change UIView parent class to UIControl, so that it can handle touchUpInside event
From:
#interface MyView : UIView
To:
#interface MyView : UIControl
and then add this line for view-
[self addTarget:self action:#selector(viewTapped:) forControlEvents:UIControlEventTouchUpInside];
Because gesture may create problem while scrolling.
My method is simply change the UIView to UIControl class in InterfaceBuilder IdentityInspector, then the ConnectionsInspector will instantly have the touch event ready for hook.
zero code changes.

Resources