Gesture recognizers on subviews don't seem to work - objective-c

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];

Related

UIScrollview with paging vertically working but user not landing to the next page

I am using the following code to scroll to the next page of a UITableView that has a scrollview. Scrolling works but the page the user lands to depends on the power he scrolls with his finger. Whatever the force of scrolling is it needs to land to the next page. How can i achieve this? Any help appreciated...
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint
*)targetContentOffset {
self.tableView.rowHeight = [UIScreen mainScreen].bounds.size.height-self.navigationController.navigationBar.frame.size.height-(self.appDelegate.heightOfEightFeedWalletTopConstraint*2)+12;
int tomove = ((int)targetContentOffset->y % (int)self.tableView.rowHeight);
if(tomove < self.tableView.rowHeight/6 || velocity.y < 0){
targetContentOffset->y -= tomove;
}
else{
targetContentOffset->y += (self.tableView.rowHeight-tomove);
}
}
You don't need any of that code...
All you need to do is enable paging on the scroll view.
This will
create a scroll view with 20-points on all 4 sides
add a vertical stack view
add 10 labels as arranged subviews (each label will be the height of the scroll view frame)
Scroll view has paging enabled:
#import <UIKit/UIKit.h>
#interface PageScrollViewController : UIViewController
{
UIScrollView *scrollView;
}
#end
#implementation PageScrollViewController
- (void)viewDidLoad {
[super viewDidLoad];
scrollView = [UIScrollView new];
[scrollView setPagingEnabled:YES];
UIStackView *stack = [UIStackView new];
stack.axis = UILayoutConstraintAxisVertical;
stack.distribution = UIStackViewDistributionFillEqually;
stack.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[scrollView addSubview:stack];
[self.view addSubview:scrollView];
// respect safeArea
UILayoutGuide *g = self.view.safeAreaLayoutGuide;
UILayoutGuide *cg = scrollView.contentLayoutGuide;
[NSLayoutConstraint activateConstraints:#[
[scrollView.topAnchor constraintEqualToAnchor:g.topAnchor constant:20.0],
[scrollView.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:20.0],
[scrollView.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:-20.0],
[scrollView.bottomAnchor constraintEqualToAnchor:g.bottomAnchor constant:-20.0],
[stack.topAnchor constraintEqualToAnchor:cg.topAnchor constant:0.0],
[stack.leadingAnchor constraintEqualToAnchor:cg.leadingAnchor constant:0.0],
[stack.trailingAnchor constraintEqualToAnchor:cg.trailingAnchor constant:0.0],
[stack.bottomAnchor constraintEqualToAnchor:cg.bottomAnchor constant:0.0],
[stack.widthAnchor constraintEqualToAnchor:scrollView.frameLayoutGuide.widthAnchor],
]];
// now let's add some views to the stack view
// making each one the same height as the scroll view frame
NSArray <UIColor *> *colors = #[
UIColor.yellowColor,
UIColor.greenColor,
UIColor.cyanColor,
UIColor.orangeColor,
];
for (int i = 0; i < 10; i++) {
UILabel *v = [UILabel new];
v.textAlignment = NSTextAlignmentCenter;
v.text = [NSString stringWithFormat:#"Label %d", i];
v.backgroundColor = colors[i % colors.count];
[stack addArrangedSubview:v];
[v.heightAnchor constraintEqualToAnchor:scrollView.frameLayoutGuide.heightAnchor].active = YES;
}
}
#end

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

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.)

Duplicating UIView

I can't figure out how could I duplicate UIView with its contents. Or is it even possible?
I have made and UIView with a label and a button inside a .xib file. Now I wish to copy this view n times only with different label text.
I'm trying to do this like this, but such way I only get the last object shown. _view1 is IBOutlet just like _view1Label.
NSMutableArray *Info = [[NSMutableArray alloc]initWithCapacity:number.intValue];
for(int i = 0; i != number.intValue; i++)
{
NSString *number = [NSString stringWithFormat:#"Zona%i ",[[NSUserDefaults standardUserDefaults] stringForKey:#"ObjectNumber"].intValue];
NSString *zona = [NSString stringWithFormat:#"%#%i",number, i+1];
NSString *parinkta = [[NSUserDefaults standardUserDefaults] stringForKey:zona];
_view1Label.text = parinkta;
_view1.hidden = false;
CGRect newrect = CGRectMake(_view1.frame.origin.x, _view1.frame.origin.y + (80 * i), _view1.frame.size.width, _view1.frame.size.height);
_view1.frame = newrect;
[Info addObject:_view1];
}
for(int i = 0; i != number.intValue; i++)
{
UIView *add = [Info objectAtIndex:i];
[self.view addSubview:add];
}
I guess you'll get the idea what I am trying to do, maybe my idea of doing this is totaly wrong, so could anyone help me get on the path on this ?
Load the view multiple times from the nib, adjusting the label contents on each copy you load. This is much easier, shorter, less prone to error than trying to copy the UIView contents in-memory which you're trying to do.
Here's an example of using UINib to access the nib contents:
UINib *nib = [UINib nibWithNibName:#"nibName" bundle:nil];
NSArray *nibContents = [nib instantiateWithOwner:nil options:nil];
// Now nibContents contains the top level items from the nib.
// So a solitary top level UIView will be accessible
// as [nibContents objectAtIndex:0]
UIView *view = (UIView *)[nibContents objectAtIndex:0];
// assumes you've set the tag of your label to '1' in interface builder
UILabel *label = (UILabel *)[view viewWithTag:1];
label.text = #"My new text";
So just repeat the above code for each nib instance you want.
if you wish to show n views then you have to create the view n times, inside the first for loop, you can not place a single view in multiple places
If you are created the first view through code then you can use the following method.
Change your for loop like:
for(int i = 0; i != number.intValue; i++)
{
NSString *number = [NSString stringWithFormat:#"Zona%i ",[[NSUserDefaults standardUserDefaults] stringForKey:#"ObjectNumber"].intValue];
NSString *zona = [NSString stringWithFormat:#"%#%i",number, i+1];
NSString *parinkta = [[NSUserDefaults standardUserDefaults] stringForKey:zona];
UIView *tempView = [[UIView alloc] init];
UILabel *tempLabel = [[UILabel alloc] init];
tempLabel.frame = _view1Label.frame;
tempLabel.text = _view1Label.text;
[tempView addSubview: tempLabel];
tempView.frame = _view1.frame;
_view1Label.text = parinkta;
_view1.hidden = false;
CGRect newrect = CGRectMake(_view1.frame.origin.x, _view1.frame.origin.y + (80 * i), _view1.frame.size.width, _view1.frame.size.height);
_view1.frame = newrect;
[Info addObject:tempView];
}

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.

NSMutableArray of UIImages - view stays stuck with last image

I'm sure that I'm missing something terribly easy here...
I loop through the images just fine the first time, the indices reset and increment properly, but the UIView doesn't refresh and keeps the last image in the array showing. transitionFromView: automatically swaps the subviews, right? Do I need to implement the completion: option?
What am I missing? Here's the pertinent code:
- (id)initWithFrame:(CGRect)frame
{
NSInteger maxSlides = 12;
self = [super initWithFrame: frame];
if (self) {
//set up an array of images to be used for a flipbook
slides = [[NSMutableArray alloc] initWithCapacity: maxSlides];
for(NSInteger i=0; i<maxSlides; ++i) {
imageView = [[UIImageView alloc] initWithImage:
[UIImage imageNamed:[NSString stringWithFormat: #"muybridge%d.png", i]]];
[slides addObject: imageView];
}
index = 0; //default to first photo.
[self addSubview: [slides objectAtIndex: index]];
}
return self;
}
- (void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event
{
//output indices for debugging. (yes, they increment properly)
NSLog(#"index = %d", index);
NSLog(#"currentIndex = %d", currentIndex);
NSLog(#"nextIndex = %d", nextIndex);
if (nextIndex <= 10) {
currentIndex = nextIndex;
} else {
currentIndex = 0;
}
nextIndex = currentIndex + 1;
//flipbook - tapping the image transitions to the next image in the array
[UIView transitionFromView: [slides objectAtIndex: currentIndex]
toView: [slides objectAtIndex: nextIndex]
duration: 0
options: UIViewAnimationOptionTransitionCurlUp
completion: NULL
];
}
What you're missing is this, from the docs on transitionFromView: toView:... :
fromView
The starting view for the transition. By default, this view is removed from its superview as part of the transition.
So it works, the first time through, but only one subview, the last one, remains after you've looked at them all. Look at the UIViewAnimationOptionShowHideTransitionViews option.
According to the Apple reference for UIView: transitionFromView: toView:... :
This method modifies the views in their view hierarchy only. It does not modify your application’s view controllers in any way. For example, if you use this method to change the root view displayed by a view controller, it is your responsibility to update the view controller appropriately to handle the change.
Maybe try calling the [yourView setNeedsDisplay] method right after.