How to increase an UIView's touch area in iOS - objective-c

According to Apple's Document, any touchable view should at least have a touchable area of 44 by 44 points.
I did some investigation, and it looks like a good solution is to over-write pointInside like following:
- (BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGRect l_bounds = self.bounds;
float lengthOf44 = [self convertLengthFromScreen:44];
CGFloat l_widthDelta = lengthOf44 - l_bounds.size.width;
CGFloat l_heightDelta = lengthOf44 - l_bounds.size.height;
if (l_widthDelta < 0)
{
l_widthDelta = 0;
}
if (l_heightDelta < 0)
{
l_heightDelta = 0;
}
l_bounds = CGRectInset(l_bounds, -0.5 * l_widthDelta, -0.5 * l_heightDelta);
if (CGRectContainsPoint(l_bounds, point))
{
return YES;
}
return NO;
}
However, this solution has many draw backs. Given a small view having a 5 by 5 pixel size.
Suppose the small view is very close to the edge of super view, then this method will cause a half of the extend touchable area outside the superview, and The touchable area is actually 22 by 44. In that case I need to further overwrite superview's pointInside.
Suppose we have three small view like which are very close to each other, Then the upper view will always win the touch.
Ideally, I want the view closest to the touch point win.
Is there a better or more elegant solution other than this please?
To better illustrate my concerns, let me give an example: Everyone should familiar with iOS's Copy Blue Hint Dot (not sure if I am using the correct word, but please see the image).
http://i.stack.imgur.com/FtjYi.png
Suppose we are going to implement it (the copy blue dot). For that blue dot, I have no idea how Apple implemented. However, I assume that blue dot is a kind of UIImageView, and assume it is a subview of some UITextView (this may be not sure). Can that UIImageView response to touch? In my solution, it can response touch by overwriting pointInSide.
My two question here in this context can be translated to be:
what if the word we are selected is too close to the UITextView edge? So the ideal touch area of that blue dot might be even outside the superview (UITextView).
what if the upper blue dot is too close to lower blue dot and we drag from middle point of two dots (it looks like lower blue dot have a higher chance to win.)

You missed the point of the docs.
Apple UI guidelines suggest that you do not make touchable areas (which are usually represented by a view of some kind, button, image whatever) less than 44X44 for usability reasons. (the size of a fingertip)
If you have a view 5X5, its not the right view to receive touch events and you should use a super view of that view to receive the touch events for the child.
(edited to remove my incorrect statement that this is not possible)

You are taking the wrong aproach, first you should not mess with the touch detection from the ios, it is preconfigured and optimized to function correctly with finger touch, thats why if u touch you usually get the reading a bit higher than where you actually touched.
It seems you want to do some sort of "where did the user touched in a place", If i was you i would get a uiview for example which is my complete touchable area and just add the rest of the elements there, you can put them wherever you want, they can even overlap or anything, just make sure the superview is the one receiving the touches and do some math to see which one was the closest to the elements center. Even if they are buttons you can disable the interaction and programatically show their state changes. as if they were pressed or something.
To restate my point. Use just 1 big touch area and do the math for the rest, in this way you will have complete control over the touch areas and such.
(Then again i could be completely misunderstanding your purpose)

Related

UIImageViews Jump around when a label changes - Xcode

I have falling UIImageViews falling on the screen and going back to the top in my game. I have another two image views on the bottom of the screen that their images change every so often. When those two bottom images change, the falling UIImageViews jump around the screen to their original position of when the game app opens, then they fall again. The same thing happens when the score label changes.
I have constraints on almost everything so im thinking that might have something to do with it but I don't know why.
Thank you very much for your help
OK so I'm sure you're aware of layout constraints and the NSLayoutConstraint class. (If not read this). Basically Apple uses these classes which are represented in the IB to position and resize subviews e.t.c.
Because of this you need to adjust the layout constraints of any view you want to move. To animate a UIView it used to be a case of changing the frame property and then getting the superview to redraw it's subviews; however, now you need to change the constant property of a view's layout constraints and then call layoutIfNeeded on the superview like so:
myImageViewConstraint.constant += 100;
[UIView animateWithDuration:1 animations:^{
[superview layoutIfNeeded];
}];
Then because you have changed the constraint, the view won't jump around.
Note: you should research this before jumping on Stack Overflow because this is readily available in the Apple Developer Resources.

UIButtons titleLabel clips Text after being rotated by CGAffineTransformMakeRotate()

I am developing a iOS-6 app. I have a UIViewController with a view that needs fixed orientation (portrait mode). But when the phone is rotated, one control on that view needs to be moved and rotated (so that it will always be in the upper left corner, and its text will be readable).
I am achieving this by shifting the control(a UIView) using the frame-property of my control (it is a custom view, more on that later), and then using CGAffineTRansformMakeRotate() afterwards, since I know that it's not advisable to use the frame after rotating a view. Everything is fine so far, but here's the thing: That custom view has three UIButtons of type UIButtonTypeCustom as its subviews. Because I rotated the View, but cannot rotate the buttons inside the view (they are not squares), I need to rotate the titleLabels of the Buttons for the text to be readable in the new deviceOrientation.
But it won't work very well. The text will be rotated, as I intended, but it will be clipped by the titleLabel, because the titleLabel has the wrong frame. I checked this by applying borders to the label. So I need to change the titleLabels frame, right? But how can I do that? I tried setting it using [titleLabel setFrame: frameThatFits];, but to no avail. (frameThatFits is a CGRect I created). Also, calling [button.titleLabel sizeToFit]; has no effect that I could see.
I am using [button setTitle:title forControlState: UIControlStateNormal];to set the title.
TL;DR: I'm trying to change the frame/bounds of a UIButtons titleLabel after rotating it using an affine transformation. Any help?
Thanks.
PS: I can supply code when needed, but I wouldn't know what to show you. Tell me what you need, I'll post it.
OK, first of all, thanks to everyone who tried to help. Im posting an alternative solution for my problem, and although it doesnt really address the problem of changing the titleLabels dimensions, it will result in the proper display of my ViewController.
It turns out using the frame is a bad idea. I initially used the frame to reposition the view and i figured that this couldnt be a problem because i only ever applied transformations afterwards, but i was wrong. Because OBVIOUSLY i tried to change the titleLabels frame. AFTER the rotation. And that didnt work.
So the way to go here is using the center-property and the bouds of the view consistently throughout the code. It will result in properly rotated Buttons, that do not need any fidgeting afterwards.
My takeaway here is that i will never ever again use the frame-property outside of a NSLog-statement. But why [button sizeToFit];wouldnt yield any results is still beyond me. If i ever figure it out, i might post it if i remember.
EDIT:
#ZevEisenberg nailed it with this comment:
“Warning: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.” So you are right to use the center and bounds here, but if you do not have a transform, the frame is perfectly safe to use.
NEXT EDIT:
Heres how i ended up repositioning the Buttons:
-(CGPoint)centerForView:(UIView *)view{
//calculate a suitableposition for the view
//depending on the current orientation and the device type (iphone 4S/5, etc)
return point;
}
Then, as a reaction to the deviceOrientation change notification, i apply CGAffineTransformIdentity to all the views, reposition them using my centerForView shown above, and apply the correct rotation transformation to the View. I do this for all the subviews every time the divice rotates, like so:
-(void)setRightRotationTransformations{
[self resetAllTransformations];
self.someSubview.transform = CGAffineTransformRotate(self.someSubview.transform, -M_PI_2);
}
In my case works such hack:
set Line Break mode to Word Wrap
Add extra line to title (even for one line title)

uiscrollview and uibutton behaves transparent for touch events

I have a custom subclass of UIView and a full screen UIButton with a background image on it as you can see in the image,
on the right side, there is a UIView with a UIImageView on it and some other not important labels and buttons. The important part is, there are two scrollviews on that UIView and at the run time, i add UIButtons ( not small ones, 200 x 100 ) on those scrollviews with vertical scrolling enabled.
The situation is really very complex so i will write as items what happens in different conditions. In my application, there are 10 of these complete screens, on a custom subclass of UIView and i use UIPanGestureRecognizer to scroll from one to other.I have a good reason not to use a scrollview for this. All the windows ( the below image, not UIWindow ) populates their content in a loop, so the running code is exact same for all of them and all of them are being created from the same .xib file. And the last information, the bigger UIButton that covers all the window has an action for touchupinside and the smaller buttons in scrollviews have their own touchupinside selectors.
Some of my windows works perfectly, if i touch anywhere on the screen, the bigger buttons action is called, if i touch and drag, the scroll works and i navigate to the next / previous window, if i touch on smaller buttons in scrollviews, their actions fired and lastly i can perfectly scroll within those buttons.
Some of my windows, when i try to scroll the scrollviews, the bigger windows pangesturerecognizer catchs this event not the small scrollviews, and if i touch the small button in that scroll view, the bigger buttons event is fired ( it pass all through uibutton > uiscrollview > uiimageview > uiview to the uibutton at the button like they dont exist )
if i replace the right container view some other position on the big uibutton, it randomly works well or not, sometimes one of the scrollviews works well and the other not.
it has a consistent behaviour, if on a one position, it works, it allways works, and if it not, it never does.
Again, all the views and subclasses have their userinteraction enabled yes, the opposite is already imposible because the behaviour changes only acording to position of container view on the big uibutton and also changes acording to big uibuttons background image.
I've placed a touchesBegan method for just test purposes, when the touches does not work as expected, the event fired with touch.view is the container of the big button, even if i touch on a small button within the scrollviews.
I've spend two days already for that and no result.What can cause this behaviour ?
EDIT : After krumelur's comment I've changed my focus from configuration of those views to animation i give to the container views, and i've noticed that, the problem is about the animation, i'm adding the the code part that animates all ten windows and behaves like a custom scrollview. All windows have their custom layer class and when i catch pangesture recognizer i move windows on the screen with following code. items in that code is an array which holds layers of all the windows. The animation causes that strange situation but i couldnt figure out yet..
- (void)layoutSublayers {
[super layoutSublayers];
[CATransaction begin];
[CATransaction setDisableActions:YES];
float angleDelta = 2 * M_PI / [items count];
float a = angle;
for (UIBaseLayer *l in items) {
l.position = self.position;
CATransform3D translation = CATransform3DMakeTranslation(cosf(a)*(radius.x), 1.0, (sinf(a)*radius.y) - radius.y*1.0);
float dailyAngle = (M_PI_2 - a);
CATransform3D rotation = CATransform3DMakeRotation(dailyAngle, 0, 1.0, 0);
CATransform3D t = CATransform3DConcat(rotation, translation);
l.transform = t;
a += angleDelta;
}
[CATransaction commit];}
http://img836.imageshack.us/img836/296/screenshot20111215at013.png
bringSubviewToFront did not work reliably on 5.0 (on 5.1 the hack was not needed)
I had to add a bigger transparrent UIButton around the button that behaved as if it was disabled to fix this on iOS prior to 5.1.
At last I've figured out that, even I send back views with CATransform3DMakeTranslation and layer.zPosition settings, touch events goes to views according to their frame position. The views that aren't currently even seen (overlayed by others) still catch touches. Writing just one line of code ([self.superview bringSubviewToFront:self]) for the view that is currently on screen solved the problem.
For testing this and understanding what I'm talking about, I've made simple test application that has only two buttons on it.
The blue and yellow buttons.The blue button overlays the yellow button so you can only see the blue one at the beginning.
On the touch up inside code for the blue button, I just send it back with buttons.layer.zPosition = -1. After this, there is no visible part of the blue button remains on screen, instead I see just the yellow button now, but when I touch the yellow button (or I think I am touching it) still blue buttons touch up inside code runs.
I'm not sure if this is the desired logic but it does not seem reasonable to me.
As I mentioned above, writing [blueButton.superview sendSubviewToBack:blueButton] while setting its layers zPosition to -1 works as expected.

How should I wrap a custom NSView in an NSScrollView?

Cocoa's NSScrollView is horribly under-explained. I hope someone here knows what it's all about and can spare me a few seconds.
So I have a custom NSView. I implement -drawRect: for it to draw something, fill itself with colour, whatever. Then I have an NSScrollView wrapping it (set up through the interface Builder).
Now the inner, custom, view must have a size larger than that which fits in the outer scroll view—for it to scroll. That much I realise. I have incidentally configured it so that the scroll view adjusts to the surrounding window’s size, but that shouldn’t matter.
I override my inner view’s -frame method to return a frame sized at least 1000x1000.
- (NSRect)frame {
CGFloat w = 1000;
CGFloat h = 1000;
if (self.superview.bounds.size.width > w)
w = self.superview.bounds.size.width;
if (self.superview.bounds.size.height > h)
h = self.superview.bounds.size.height;
return NSMakeRect(0, 0, w, h);
}
Here’s the outcome, which I have trouble interpreting:
I can scroll when the scroll view encloses an area smaller than 1000x1000
BUT
The only area filled with colour (i.e. that my -drawRect: method has any effect on) is
as large as the scroll view’s bounds
located at (0,0. I use flipped, so that’s top left, and it ends up being outside the visible area after scrolling.
The visible area that lies outside this irrelevant rectangle is not painted at all.
I don’t know anything beyond this point. It seems like the rect for drawing is clipped to the scroll view’s position in the window, and size, or something—but it does not take the scrolled "location" into account.
It should be noted that I don't really expect anything else to happen. I feel I am missing a piece, but can't find which. Sorry for the wall of text, but I can’t explain better right now. I hope it is easier to answer than it is to ask.
Regards and hope,
Not Rick Astley
It's a very very very bad idea to overwrite -frame. There is so much that depends on the actual instance variable having a correct value. Instead try to set the frame to the one you want using setFrame:, that might fix all your problems if you're lucky...
I agree with Max's warning that you shouldn't override -frame. If you want to constrain the set frame, override its setter ( -setFrame: ) and the designated initializer ( -initWithFrame: ) and adjust the proposed frame as desired.
Regarding your overall problem, I wonder if your problem is conceptual. The argument for -drawRect: (the dirty rectangle you're asked to redraw) is useful if you're drawing something that you can redraw incrementally in parts (like a grid - any grid blocks intersecting dirtyRect can be redrawn and the rest can be ignored). If you're doing something that has to be completely redrawn, you should use [self bounds] and not the dirty rect passed at drawRect.
For example, if you have just a standard gradient background, it's difficult to tell from dirtyRect which part of the gradient to redraw and infinitely easier just to redraw the whole view with the gradient, ignoring dirtyRect altogether.
You're right in assuming that only the area of your view exposed by the scroll view's clip rect will normally be asked to redraw when scrolling. There're also interactions with the scroll view's -copiesOnScroll to consider.
I hope this helps.
Use of the NSScroller really relies on a solid understanding of the MVC paradigm. Apple's docs really focus on showing a photo and a set of text, but not much else. The use of NSScrollView is something that I've struggled with in the past.
First off, do not override frame. Use setFrame to tell the scrollView how large the working area is, and then just simply draw in the area the frame encompasses. As I understand it, a custom NSView and the encompassing NSScrollView takes care of the rest, such as what to draw where when. In other words, ignore the bounds of the rect passed into drawRect and instead draw within the bounds of the frame you sent to scrollView; don't worry about what is visible and what isn't because that is the job of the framework.
Here is where the MVC paradigm comes in: setFrame should be used when your Model is updated. So, if an object falls outside of the current bounds of the frame, then use setFrame to set the newly expanded bounds, and then draw within that area.

iPhone UIScrollView, Slow down the scrolling

How do I add some extra drag to the UIScrollView physics. It scrolls just a little too fast for what I am doing. (I don't want to disable altogether, I still like the rubber band effect when you get to the end or beginning of the view.) Is there any way to slow it down?
Actually, after reading the Documentation I discovered the property for UIScrollView that slows down the scrolling, so Apple does make this readily available for anyone else looking for this:
scrollView.decelerationRate = UIScrollViewDecelerationRateFast;
Even though it says fast, it is speeding up deceleration which in effect slows the scrolling down. This was exactly what I needed. And no worry of patents. :)
Actually you can make block animation with ScrollToRowAtIndexPath:... with animated set to NO. This way block animation will animate underlaying variables manually using selected time.
As UICollectionView is subclass of UIScrollView You can use scroll view delegate method.
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
targetContentOffset->x = (targetContentOffset->x - scrollView.contentOffset.x) / 6 + scrollView.contentOffset.x;
targetContentOffset->y = (targetContentOffset->y - scrollView.contentOffset.y) / 6 + scrollView.contentOffset.y;
}
This is simply not possible right now. Maybe Apple will one day extend the API to allow it, but until that day, you're stuck with the default speed.
The only other option would be your own scrolling implementation. This isn't trivial if you want all the nice polish such as the rubber band effect and the way the scrolling will lock horizontally or vertically. Not to mention flicking to scroll.
Also, Apple has a patent for some of those scrolling behaviours, so writing your own might even be dangerous. Source: "Apple's touch-screen patent".
A computing device, comprising: a touch screen display; one or more processors; memory; and one or more programs, wherein the one or more programs are stored in the memory and configured to be executed by the one or more processors, the one or more programs including: instructions for detecting one or more finger contacts with the touch screen display; instructions for applying one or more heuristics to the one or more finger contacts to determine a command for the device; and instructions for processing the command; wherein the one or more heuristics comprise: a vertical screen scrolling heuristic for determining that the one or more finger contacts correspond to a one-dimensional vertical screen scrolling command rather than a two-dimensional screen translation command based on an angle of initial movement of a finger contact with respect to the touch screen display; a two-dimensional screen translation heuristic for determining that the one or more finger contacts correspond to the two-dimensional screen translation command rather than the one-dimensional vertical screen scrolling command based on the angle of initial movement of the finger contact with respect to the touch screen display; and a next item heuristic for determining that the one or more finger contacts correspond to a command to transition from displaying a respective item in a set of items to displaying a next item in the set of items.
But of course, I am not a lawyer.