creating a line between two UIlabels using the long press gesture recognizer - objective-c

i am developing a ER diagram editor, i have a bunch of draggable UILabels but all of them have the same name. i want to be able to create a line between two UIlabels when both are pressed together using the long press gesture recognizer. any help will be most appreciated

You can create your long press gesture on the superview shared by these two labels, e.g.:
UILongPressGestureRecognizer *twoTouchLongPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:#selector(handleLongPress:)];
twoTouchLongPress.numberOfTouchesRequired = 2;
[self.view addGestureRecognizer:twoTouchLongPress];
You can then write a gesture handler:
- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan)
{
CGPoint location0 = [gesture locationOfTouch:0 inView:gesture.view];
CGPoint location1 = [gesture locationOfTouch:1 inView:gesture.view];
if ((CGRectContainsPoint(self.label0.frame, location0) && CGRectContainsPoint(self.label1.frame, location1)) ||
(CGRectContainsPoint(self.label1.frame, location0) && CGRectContainsPoint(self.label0.frame, location1)))
{
NSLog(#"success; draw your line");
}
else
{
NSLog(#"failure; don't draw your line");
}
}
}
In the updated comments, you suggest that you're creating a local UILabel variable, and then adding the resulting label to the view. That's fine, but you really want to maintain a backing model, that captures what you're doing in the view. For simplicity's sake, let me assume that you'll have array of these labels, e.g.:
#property (nonatomic, strong) NSMutableArray *labels;
Which you then initialize at some point (e.g. viewDidLoad):
self.labels = [[NSMutableArray alloc] init];
Then as you add labels to your view, add a reference to them in your array:
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(xVal, yVal, 200.0f, 60.0f)];
label.text = sentence;
label.layer.borderColor = [UIColor blueColor].CGColor;
label.layer.borderWidth = 0.0;
label.backgroundColor = [UIColor clearColor];
label.font = [UIFont systemFontOfSize:19.0f];
[self.view addSubview:label];
[self.labels addObject:label];
Then, your gesture can do something like:
- (UILabel *)labelForLocation:(CGPoint)location
{
for (UILabel *label in self.labels)
{
if (CGRectContainsPoint(label.frame, location))
return label; // if found one, return that `UILabel`
}
return nil; // if not, return nil
}
- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan)
{
CGPoint location0 = [gesture locationOfTouch:0 inView:gesture.view];
CGPoint location1 = [gesture locationOfTouch:1 inView:gesture.view];
UILabel *label0 = [self labelForLocation:location0];
UILabel *label1 = [self labelForLocation:location1];
if (label0 != nil && label1 != nil && label0 != label1)
{
NSLog(#"success; draw your line");
}
else
{
NSLog(#"failure; don't draw your line");
}
}
}
Frankly, I'd rather see this backed by a proper model, but that's a more complicated conversation beyond the scope of a simple Stack Overflow answer. But hopefully the above gives you an idea of what it might look like. (BTW, I just typed in the above without assistance of Xcode, so I'll apologize in advance for typos.)

Related

Adding UISearchBar in UINavigationBar with constraints

All,
I want to add UISearchBar to UINavigationbar, I dont dont want to use UISearchController, Just UISearchbar programmatically and it must work in landscape as well.
I tried it is working well in Portrait well, but in Landscape, i have issues in iPhone X width. Can we use Constraints.
Below is the code
CGFloat width = [UIScreen mainScreen].bounds.size.width;
search = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, width - 2 * 44 - 2 * 15, 44)];
search.delegate = self; // search.tintColor = [UIColor redColor];
search.searchBarStyle = UISearchBarStyleMinimal;
search.placeholder = #"Search";
search.translucent = NO;
search.opaque = NO;
search.showsCancelButton = NO;
[search setBackgroundImage:[[UIImage alloc] init]];
//customize textfield inside UISearchBar
#try {
for (id object in [[[search subviews] firstObject] subviews])
{
if (object && [object isKindOfClass:[UITextField class]])
{
UITextField *textFieldObject = (UITextField *)object;
textFieldObject.backgroundColor = [UIColor whiteColor];
textFieldObject.borderStyle = UITextBorderStyleRoundedRect;
textFieldObject.layer.borderColor = (__bridge CGColorRef _Nullable)([brandingObj getValueForKey:navBarTitleColor]);
textFieldObject.layer.borderWidth = 1.0;
break;
}
}
}
#catch (NSException *exception) {
NSLog(#"Error while customizing UISearchBar");
}
#finally {
}
I don't understand why you want to use UISearchDisplayController, its highly configurable and recommended by apple, working with geometry (CGRecr, CGFrame, etc.) to adjust UIKit objects layout could be a pain, use auto layout avoiding UIKits convenience initializers, to adjust later constraints.
Anyway if you want explicitly do with this way, this should work.
#import "ViewController.h"
#interface ViewController ()<UISearchBarDelegate>
#property UISearchBar *searchbar;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_searchbar = [UISearchBar new];
_searchbar.delegate = self;
_searchbar.searchBarStyle = UISearchBarStyleMinimal;
self.navigationItem.titleView = self.searchbar;
// Do any additional setup after loading the view, typically from a nib.
}
Maybe you should need configure appearance , take a look here
Cheers.

UILabel stops animations

I have 5 UIImageViews getting animated down the screen. If one is pressed, and it meets the requirements, it will add 1 to your score, using this:
self.score.text = #(self.score.text.integerValue + 1).stringValue;
But when the text in the UILabel updates, all the animations stop abruptly and the remaining UIImageViews disappear. But, the animation only restarts after a few seconds, as if the images are becoming hidden. The code to animate the images and change image(They are the same for each one):
- (void) squareOneMover {
NSUInteger r = arc4random_uniform(3);
[self.squareOne setHidden:NO];
CGPoint originalPosition = self.squareOne.center;
CGPoint position = self.squareOne.center;
originalPosition.y = -55;
position.y += 790;
[self.squareOne setCenter:originalPosition];
[UIView animateWithDuration:r + 3
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
[self.squareOne setCenter:position];
}
completion:^(BOOL complete) {
if (complete) {
[self squareOneColour];
[self squareOneMover];
}
}
];
}
- (void) squareOneColour {
NSUInteger r = arc4random_uniform(5);
[self.squareOne setImage:[self.colorArray objectAtIndex:r]];
}
Anyone have a solution? And if by changing the text in a UILabel is supposed to stop animations (I don't know why they would do so) can someone provide a workaround to make keeping score possible.
Edit: I created a button that would increase the integer value of score. This means I can change the text in the UILabel manually. The animations still stopped the moment the text changed.
Edit 2: Method for the pressed event:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
NSUInteger a = [self.colorArray indexOfObject:self.squareOne.image];
NSUInteger b = [self.iconColorArray indexOfObject:self.icon.image];
if ([self.squareOne.layer.presentationLayer hitTest:touchLocation]) {
_squareOne.hidden = YES;
}
if (a!=b) {
if (self.squareOne.hidden==YES) {
NSLog(#"NO");
}
}
if (a==b) {
if ([self.squareOne.layer.presentationLayer hitTest:touchLocation]) {
self.score.text = #(self.score.text.integerValue + 1).stringValue;
}
}
if ([self.squareTwo.layer.presentationLayer hitTest:touchLocation]) {
_squareTwo.hidden = YES;
}
}
Looking at Matt's answer, how would I animate the UIImageView by "changing its constraints"?
Constraints:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-25-[btn1]-25-[btn2(==btn1)]-25-[btn3(==btn1)]-25-[btn4(==btn1)]-25-[btn5(==btn1)]-25-|" options:0 metrics:nil views:views]];
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:self.squareOne
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:0.0
constant:-55.0]];
The problem is this line:
[self.squareOne setCenter:position];
You are animating the position of squareOne by setting its position. But meanwhile you also have constraints that also position squareOne. That fact remains concealed until you change the text of the label; that triggers layout, and now the constraints all assert themselves, putting an end to everything else that was going on.
One solution is to animate the position of squareOne by changing its constraints. Now when layout is triggered, the existing constraints will match the situation because they are the only force that is positioning things.
//what all are you checking with the if's?
//I'm just curious there's a lot going on here
**//if this evaluates true
if ([self.squareOne.layer.presentationLayer hitTest:touchLocation]) {
_squareOne.hidden = YES;
}**
if (a!=b) {
if (self.squareOne.hidden==YES) {
NSLog(#"NO");
}
}
if (a==b) {
** //this will evaluate true also
if ([self.squareOne.layer.presentationLayer hitTest:touchLocation]) {
self.score.text = #(self.score.text.integerValue + 1).stringValue;
}**
}
//this works fine even when I added everything from interface builder which almost never happens
Update
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (strong, nonatomic)UILabel *label;
#property int tapCount;
#end
#implementation ViewController
-(void)viewDidLoad{
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]init];
[tap addTarget:self action:#selector(userTap:)];
[self.view addGestureRecognizer:tap];
}
-(UILabel*)label{
if (!_label) {
_label = [[UILabel alloc]init];
_label.text = #"taps:%3i",0;
[_label sizeToFit];
_label.center = self.view.center;
[self.view addSubview:_label];
}
return _label;
}
-(void)userTap:(UITapGestureRecognizer*)sender {
NSLog(#"tap");
self.tapCount ++;
[UIView animateWithDuration:1
animations:^{
self.label.text = [NSString stringWithFormat:#"taps:%i",self.tapCount];
self.label.center = [sender locationInView:self.view];
}
completion:nil];
}
#end
**just cleaning up the code a bit for future onlookers **
direct copy and paste only delete everything inside ViewController.m
on a new project and paste this in it's place
Instead of animating the constraints by hand, you can also have UIImageView generate constraints during animation (which does not get broken by a sibling UILabel). Like in this question.

Render Title of MKPolygon

I'm trying to render MKPolygon using the following code:
NSMutableArray *overlays = [NSMutableArray array];
for (NSDictionary *state in states) {
NSArray *points = [state valueForKeyPath:#"point"];
NSInteger numberOfCoordinates = [points count];
CLLocationCoordinate2D *polygonPoints = malloc(numberOfCoordinates * sizeof(CLLocationCoordinate2D));
NSInteger index = 0;
for (NSDictionary *pointDict in points) {
polygonPoints[index] = CLLocationCoordinate2DMake([[pointDict valueForKeyPath:#"latitude"] floatValue], [[pointDict valueForKeyPath:#"longitude"] floatValue]);
index++;
}
MKPolygon *overlayPolygon = [MKPolygon polygonWithCoordinates:polygonPoints count:numberOfCoordinates];
overlayPolygon.title = [state valueForKey:#"name"];
[overlays addObject:overlayPolygon];
free(polygonPoints);
}
[self.stateMapView addOverlays:overlays];
I used the following code to provide stroke and fill colors:
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay NS_AVAILABLE(10_9, 7_0);
{
if ([overlay isKindOfClass:[MKPolygon class]])
{
MKPolygonRenderer *pv = [[MKPolygonRenderer alloc] initWithPolygon:overlay];
pv.fillColor = [UIColor redColor];
pv.strokeColor = [UIColor blackColor];
return pv;
}
return nil;
}
Do I need to do something to render the Title? I think I should enable a configuration or something but I'm new to MapView. Or I need to create a UILabel?
Overlays don't automatically show their titles like annotations can (in their callout actually) so there's nothing you "need to do" or any configuration that you can enable.
A simple workaround to show titles on overlays is, as you suggest, to create a UILabel.
However, this UILabel should be added to an annotation view that is positioned at each overlay's center.
A minor drawback (or maybe not) to this method is that the titles will not scale with the zoom of the map -- they'll stay the same size and can eventually collide and overlay with other titles (but you may be ok with this).
To implement this approach:
For each overlay, add an annotation (using addAnnotation: or addAnnotations:) and set the coordinate to the approximate center of the overlay and the title to the overlay's title.
Note that since MKPolygon implements both the MKOverlay and the MKAnnotation protocols, you don't necessarily need to create a separate annotation class or separate objects for each overlay. MKPolygon automatically sets its coordinate property to the approximate center of the polygon so you don't need to calculate anything. You can just add the overlay objects themselves as the annotations. That's how the example below does it.
Implement the mapView:viewForAnnotation: delegate method and create an MKAnnotationView with a UILabel in it that displays the title.
Example:
[self.stateMapView addOverlays:overlays];
//After adding the overlays as "overlays",
//also add them as "annotations"...
[self.stateMapView addAnnotations:overlays];
//Implement the viewForAnnotation delegate method...
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
{
//show default blue dot for user location...
return nil;
}
static NSString *reuseId = #"ann";
MKAnnotationView *av = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (av == nil)
{
av = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId];
av.canShowCallout = NO;
//add a UILabel in the view itself to show the title...
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 50, 30)];
titleLabel.tag = 42;
titleLabel.backgroundColor = [UIColor clearColor];
titleLabel.textColor = [UIColor blackColor];
titleLabel.font = [UIFont boldSystemFontOfSize:16];
titleLabel.textAlignment = NSTextAlignmentCenter;
titleLabel.minimumScaleFactor = 0.5;
[av addSubview:titleLabel];
av.frame = titleLabel.frame;
}
else
{
av.annotation = annotation;
}
//find the UILabel and set the title HERE
//so that it gets set whether we're re-using a view or not...
UILabel *titleLabel = (UILabel *)[av viewWithTag:42];
titleLabel.text = annotation.title;
return av;
}
The alternative approach is to create a custom overlay renderer and do all the drawing yourself (the polygon line, the stroke color, the fill color, and the text). See Draw text in circle overlay and Is there a way to add text using Paths Drawing for some ideas on how to implement that.

UIScrollView with UIImageview and gestures using transitionFromView acting strange

I made a view which holds a UIScrollview:
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(10, 65, 300, 188)];
//BackViews will hold the Back Image
BackViews = [[NSMutableArray alloc] init];
for (int i=0; i<BigPictures.count; i++) {
[BackViews addObject:[NSNull null]];
}
FrontViews = [[NSMutableArray alloc] initWithCapacity:BigPictures.count];
[self.pageControl setNumberOfPages:BigPictures.count];
Then I add several UIImageviews containing images:
//BigPictures holds objects of type UIImage
for (int i = 0; i < BigPictures.count; i++) {
UIImageView *ImageView = [[UIImageView alloc] initWithImage:[BigPictures objectAtIndex:i]];
ImageView.frame = [self.scrollView bounds];
[ImageView setFrame:CGRectMake(self.scrollView.frame.size.width * i, ImageView.frame.origin.y, ImageView.frame.size.width, ImageView.frame.size.height)];
//this saves the FrontView for later (flip)
[FrontViews insertObject:ImageView atIndex:i];
[self.scrollView addSubview:test];
}
// Detect Single Taps on ScrollView
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(flip)];
[self.scrollView addGestureRecognizer:tap];
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * BigPictures.count, self.scrollView.frame.size.height);
self.scrollView.pagingEnabled = YES;
self.scrollView.delegate = self;
Ok so far so good. Now the method which does the flipImage part:
- (void)flip {
int currentPage = self.pageControl.currentPage;
UIView *Back = nil;
if ([BackViews objectAtIndex:currentPage] == [NSNull null]) {
//CreateBackView is just creating an UIView with text in it.
Back = [self CreateBackView];
[BackViews replaceObjectAtIndex:currentPage withObject:Back];
[UIView transitionFromView:[[self.scrollView subviews] objectAtIndex:currentPage] toView:[BackViews objectAtIndex:currentPage] duration:0.8 options:UIViewAnimationOptionTransitionFlipFromLeft completion:NULL];
} else {
[UIView transitionFromView:[[self.scrollView subviews] objectAtIndex:currentPage] toView:[FrontViews objectAtIndex:currentPage] duration:0.8 options:UIViewAnimationOptionTransitionFlipFromRight completion:NULL];
[BackViews replaceObjectAtIndex:currentPage withObject:[NSNull null]];
}
[self.view addSubview:Back];
[self rebuildScrollView];
}
This is what rebuildScrollView does:
- (void)rebuildScrollView
{
for (UIView *subview in self.scrollView.subviews) {
[subview removeFromSuperview];
}
for (int i = 0; i < BigPictures.count; i++) {
if ([BackViews objectAtIndex:i] == [NSNull null]) {
[self.scrollView addSubview:[FrontViews objectAtIndex:i]];
} else {
[self.scrollView addSubview:[BackViews objectAtIndex:i]];
}
}
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * BigPictures.count, self.scrollView.frame.size.height);
self.scrollView.pagingEnabled = YES;
self.scrollView.delegate = self;
}
So the behavior is the following:
If I click on the first image (1 of 3 in scrollview) the effect is the way I want it, meaning the frontimage turns around and shows the empty (white) back with some text in the middle
If I click on the second image, the image turns but the back is completely empty showing the grey background of the window. If I scroll to the other images, the still show the front image (as expected)
Now I click on the third image and its the same as 1) great.
Current layout is now [BackView, Nothing, Backview)
Lets run that again. But now I click on the last image and its the same as 2) :(
Any ideas whats going wrong here?
EDIT: Some new findings. I doubled the amount of pictures and this is how Front and Backviews are placed (after flipping each one). P = Picture & B = Backview.
P_1(B_1) - actually the only correct one
P_2(empty - should be B_2)
P_3(B_2 - should be B_3)
P_4(empty - should be B_4)
P_5(B_3 - should be B_5)
P_6(empty - should be B_6)
Did a complete rebuild and now it works. Really strange because I used the exact same code.

Gesture recognizers on subviews don't seem to work

I have code as the following:
- (void)setPosts:(NSArray *)posts
{
_posts = posts;
int totalHeight = 0;
for (TumblrPost *post in posts) {
totalHeight += post.thumbH;
}
dispatch_queue_t mainQ = dispatch_get_main_queue();
dispatch_async(mainQ, ^{
int cumulativeY = 0;
int postCount = 0;
for (TumblrPost *post in posts) {
NSArray* array = [[NSBundle mainBundle] loadNibNamed:#"ThumbnailView" owner:nil options:nil];
ThumbnailView* thumbnail = [array objectAtIndex:0];
thumbnail.frame = CGRectMake(0,cumulativeY,0,0);
thumbnail.userInteractionEnabled = YES;
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(showMainImage:)];
[thumbnail addGestureRecognizer:gesture];
[self.multiThumbnailView addSubview:thumbnail];
[thumbnail loadUrl:post.url];
cumulativeY+=100;//post.thumbH;
if(postCount >=2)
break;
postCount++;
}
NSLog(#"Set posts method");
});
}
- (void)showMainImage:(UITapGestureRecognizer *)gesture
{
if(gesture.state == UIGestureRecognizerStateChanged || gesture.state == UIGestureRecognizerStateEnded)
{
int thumbIndex = [self.multiThumbnailView.subviews indexOfObject:gesture.view];
self.selectedPost = (TumblrPost*)[self.posts objectAtIndex:thumbIndex];
[self performSegueWithIdentifier:#"ShowMainPost" sender:self];
}
}
multiThumbnailView is a UIView which I have in my storyboard and ThumbnailView is an xib/class combination which is a 100x100 square with a label in it that says 'test'.
When I run my code I get three boxes in a vertical line but the gesture recogniser doesn't fire when I click on my sub views. Everything has userInteractionEnabled ticked. I tried making a test gesturerecognizer on the main multiThumbnailView and that worked.
Please help!
My guess is that ThumbnailView is a subclass of UIImageView - which by default sets its userInteractionEnabled to NO.
Make sure that you set userInteractionEnabled = YES on every ThumbnailViewthat you want to intercept taps.
EDIT:
In addition, you set the frame of the ThumbnailView to be with size of (0,0).
That means that this view is basically invisible and thus won't intercept touches.
And finally, please don't do:
ThumbnailView* thumbnail = [array objectAtIndex:0];
Instead you can check the count of the array or simply:
ThumbnailView* thumbnail = [array lastObject];