Is it possible to create two button shown as below image? It may seem like you should use UIButton or UIImageView but if i click to area 1, it stills acts as clicked to button 1. Button 2 should be fired when I click to the area 1 as well!
Failing the above responses being acceptable, you could implement a custom subclass of UIButton that overrides pointInside:withEvent:.
Assuming your view is exactly square and the graphic exactly circular and filling the entire square, an example might be:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
// check that the point is at least inside the normal rect
if(![super pointInside:point withEvent:event]) return NO;
// we'll assume a square view with an exact circle inside, so
// the radius of the circle is just half the width
CGFloat radius = self.bounds.size.width*0.5f;
// the point (radius, radius) is also the centre of the view, so...
CGFloat squareOfDistanceFromCentre =
(radius - point.x)*(radius - point.x) +
(radius - point.y)*(radius - point.y);
// if the point is too far away, return NO
if(squareOfDistanceFromCentre > radius*radius) return NO;
// otherwise we've exhausted possible reasons for answering NO
return YES;
}
You can make circular button by cutting the layer and set radius of you button.
[[button layer] setCornerRadius:8.0f];
you can also try with change radius.
Yes, of course is possible.
You can connect a single action with more than one selector through IB.
You can also call directly the method fired by button2 from inside the method fired by button1.
It quite tricky, but possible:
You can use only one button, but put some verification after event touchUpInside. You should calculate if this touch point are inside the circle of "button1". For this task you need to have some mathematic knowledge - How do I calculate a point on a circle’s circumference?
Set userInteractionEnabled to NO for the smaller Button 1. All events will go to the larger Button 2.
I have created two round rect buttons for this purpose, one of them is long and thin, and the other one is more wide. Together they build a shape like a chubby plus sign, which is pretty close to a circle, considering apple accepts 44 px as the minimum confortably pressable size. If you want the image to change, set another image to its imageView for highlighted state and connect several actions (touchup wont be sufficient if you want to immitate buttons highlighted state on the image view)of the two buttons. Alternatively you can add observers and alter highlighted state of the imageview according to the buttons
A clean way to deal with pointInside on a circular button is this:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if (![super pointInside:point withEvent:event])
{
return NO;
}
BOOL isInside = (pow((point.x-self.frame.size.width/2), 2) + pow((point.y - self.frame.size.height/2), 2) < pow((self.frame.size.width/2), 2)) ? YES:NO;
return isInside;
}
You can ditch the 'isInside' variabe, but this way is easier to test.
Related
What i'm trying to do is "navigating" into a bigger UIView with buttons and controls positioned in different places. I've made the size of the main UIView twice bigger than the usual. It is 640x480 instead of 320x480.
After clicking a button in the first part of the screen i've made a moving translation of -320px in the x-direction to show the second "hidden" part of the screen where other functions will reveals to the user. Everything works perfect apart the fact i can't get the UIView back to the original position. It seems that the button i use to get back to the original position and that is "outside the bounds" of the 320px, doesn't work.
My UIView is referenced as "introScreen", and the following is the function i call to translate the screen through the x direction:
- (void)secondPartOfTheScreen {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.15];
introScreen.transform = CGAffineTransformMakeTranslation(-320, 0);
[UIView commitAnimations];
}
Calling this function my UIView moves correctly, but when my button appear in this part of the screen, it doesn't get any user interaction. If i move that button in the first part of the screen, it works correctly. It have something to do with the screen bounds? It is possibile to solve it?
Thanks in advance!
You should try to subclass your UIView and override its pointInside:withEvent: method so that also a point outside its bounds is recognized as belonging to the view. Otherwise, there is no chance that user interaction outside of the view bounds are handled as you would like to.
This is what the Event Handling Guide say:
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.
You could use something like this in your custom UIView:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
for (UIView* view in self.subviews) {
if (view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
return YES;
}
}
return NO;
}
You could do with this:
#interface MyView: UIView
#end
#implementation MyView
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
for (UIView* view in self.subviews) {
if (view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
return YES;
}
}
return NO;
}
#end
add this class definition to the beginning of the .m file where you are using the view. (You could also use separate .h/.m files, but for the sake of testing if everything works, it is enough). Then, replace UIView with MyView in the instantiation of your current UIView, and it should work.
You're not using the transform property properly, you should take into account the previous value of the property like this:
introScreen.transform = CGAffineTransformConcat(introScreen.transform, CGAffineTransformMakeTranslation(-320, 0));
Hope this helps, and check Apple's UIView documentation and CGAffineTransform documentation for more details.
2 things.
First why would you use transform when you are merely moving the object. You can set the frame just as easily.
CGRect frame = introScreen.frame;
frame.origin.x -= 320;
introScreen.frame = frame;
(Yes this will work inside an animation)
second, if you translate a view its contents should be within its bounds regardless of the size you want displayed in the first place.
therefore your container view (the one with the buttons) should be a size so that all contained pieces are within its bounds. Anything outside its bounds will not function. Having the main view twice its size is not as useful as having the contained view at twice its size.
Think of it like this.
Your table should be able to hold whatever your looking at. If you have a map you want to display you would slide it around on the table. the table does not need to be as big as the map but the map needs to be big enough to present you with all of its contents. if the map was say too short to display the poles and you would be without the information. No matter how much you transformed the map you would not be able to use the north or south poles..
To ensure the app reflects this you can turn on clip subviews on all the views in question, then if its outside the bounds it will not be visible.
Is there a way the Round Rect button to take exactly the same size of an image?Are there any round buttons? I have a project with many buttons-images and they get mixed together. The images are mostly circular and the buttons Rectangular, so when I place them close to each other they get mixed.
When the iPhone detects a touch on the screen, it finds the touched view using “hit testing”. By default, hit testing assumes that each view is a rectangle.
If you want hit testing to treat your view as a different shape, you need to create a subclass (of UIButton in your case) and override the pointInside:withEvent: method to test the shape you want to use.
For example:
#implementation MyOvalButton
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:self.bounds];
return [path containsPoint:point];
}
I haven't tested that code.
Swift version:
class MyOvalButton: UIButton {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return UIBezierPath(ovalIn: bounds).contains(point)
}
Don't forget to set your button's custom class to MyOvalButton in your storyboard or xib, if that's where you create the button.
Here's a demo, where I have connected the touch-down and touch-up events of the button to turn the background gray when the button is touched:
Answering on your question in topic:(hoping I understood what you really want)
[button setFrame:CGRectMake(button.frame.origin.x, button.frame.origin.y, image.size.width, image.size.height)];
That makes the button's frame be the same as the size of your image.
Is it possible to pan a UIImageView and when it intersects with the frame of another UIImageView, have that other UIImageView be pushed around by the other UIImageView without just passing over it? I hope that makes sense!
If it is possible, could you give me some ideas on how I'd go about implementing this?
I imagine it would go something like...
If frame intersects frame from left or right on the x/y axis, have the other frame move in that same direction with same distance as the pushing frame. While that logic somewhat makes sense to me, I'm not sure how I'd implement that in code.
I'd really appreciate any advice you can offer.
So, you're only concerned with left / right panning...
Let's assume you have 2 UIImageViews, a & b, as subviews of some other UIView and defined as class members of some class.
You can get help detecting panning gestures (dragging touches) on each of these views by creating instances of UIPanGestureRecognizer and adding them to each UIImageView using method addGestureRecognizer:
When creating each UIPanGestureRecognizer, you need to designate a selector to receive the gesture events. Let say it's called didMove:
Now, some sample code:
- (void) didMove:(UIPanGestureRecognizer*)recognizer {
UIView* view = [recognizer view];
// Remember our UIImageViews are class members called, a & b
//
UIView* otherView = view == a ? b : a;
if (recognizer.state == UIGestureRecognizerStateBegin
|| recognizer.state == UIGestureRecognizerStateChanged) {
// Moving left will have a negative x, moving right positive
//
CGPoint translation = [recognizer translationInView:view.superview];
view.center = CGPointMake(view.center.x + translation.x, view.center.y);
// Reset so we always track the translation from the current view position
//
[recognizer setTranslation:CGPointZero inView:view.superview];
if (CGRectIntersectsRect(view.frame, otherView.frame)) {
// Translate by the same number of points to keep views in sync
//
otherView.center = CGPointMake(otherView.center.x + translation.x, otherView.center.y);
}
}
}
It may not be exactly what you need, but it should give you a good idea how it could be done.
Sounds like you'd better use something like Cocos2D with it's sprites, intersections and some basic physics...
You can do it like this,
First, Create an UIImageView and assign an UIImage to it.
Second, When the user swipes or makes any gesture just change the coordinates of the card by setting its frame again.
Since u want the card to slide away, you have to change the x coordinate to a negative value such that it slides off from the view.
The above said code of reframing the imageview should be given within a UIView setAnimation group of methods...with a required delay.
To achieve the push effect have a method called within the the same animation block of codes which creates a new imageview (having same name as that of first imageview) and keep it out of the view. That is beyond the width of the screen. (dont forget to release the card which was pushes away previously)
Then subsequently again make a new frame for this imageview such that it appears in the centre of the screen....When above three steps of code are put within the animation block of code for UIView or UIImageView with a delay time , u will achieve the required effect.
Hope this helps....
happy coding....
Is it possible to display a tap-to-focus blue box when a custom overlay is used in a UIImagePickerView and when showsCameraControl attribute is set to FALSE?
The modal camera view already supports touch-to-focus. You need to make your overlay view "transparent" to touches.
Subclass a UIView as OverlayView and add something like this. In my overlay view I have two buttons, which should of course not be transparent for touching.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if (CGRectContainsPoint(infoButton.frame, point) || CGRectContainsPoint(snapButton.frame, point)) {
// touched button
return YES;
}
return NO;
}
There are probably other and more elegant ways to do this.
I think you would also have to add the little "focus rectangle" on your view programmatically.
you can use a custom button for that with alpha value zero. And with text that you want to display
I have a draggable view that a user will touch, but some rectangles of it will have no image (alpha 0).
When a user clicks the transparent region (I am able to construct the transparent region without the alpha info), I want the view (same class) below the transparent region to detect the touch.
My strategy is to let the view ignore the touch when user touches the transparent area and hope the view below it will automatically catch the touch event. But I'm not sure if this will work. (setting things up to test this will take some time)
Should I take a different approach or the above strategy would work?
Thank you.
Try overwriting the method hitTest:withEvent: in the superview. You can make hitTest:withEvent: return the view you want to handle a given event.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
for (UIView *v in self.subviews){
CGPoint pointInB = [v convertPoint:point fromView:self];
if ([v someConditionYouMayWantToTestFor]){
return v;
}
}
return nil;
}
The method someConditionYouMayWantToTestFor is where you test if you want the subview to capture the event or not.