Touch UITableView background while scrolling (Kind of an easter egg) - objective-c

I need to do a hidden button at the bottom of the table view, not in a cell, in the background of the table view itself.
I saw something like this in a game "Where is my water", if you scroll out of bounds you can find a hidden button.
first, I created a simple button, and placed in the bottom of the table view
self.tableView.backgroundColor = [UIColor purpleColor]; //just to see better
UIButton *venom = [[UIButton alloc] initWithFrame:CGRectMake(50.0, self.tableView.contentSize.height+80.0, 100.0, 40.0)];
venom.backgroundColor = [UIColor redColor];
[venom addTarget:self action:#selector(venomAction) forControlEvents:UIControlEventTouchDown];
[self.tableView addSubview:venom];
Because of the self.tableView.contentSize.height+80.0, I need to scroll out of the bounds of the table view to see the button, having something like this:
The result is correct, I want this to be sort of hidden, but the problem is, I cannot click on the button, to see the button, I need to be scrolling and I cannot multitouch.
Can anyone assist me with that? or point me to the right direction

I'd make the button not the tableView's subview, but rather a sibling view above the tableView.
For example, your code could look like this
- (void)viewDidLoad {
[super viewDidLoad];
// Just creating a simple UITableView
UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
tableView.dataSource = self;
tableView.delegate = self;
self.tableView = tableView;
[self.view addSubview:tableView];
// Creating the Easter Egg Button
UIButton *easterEggButton = [UIButton buttonWithType:UIButtonTypeSystem];
[easterEggButton setTitle:#"Easter Egg Button" forState:UIControlStateNormal];
[easterEggButton addTarget:self
action:#selector(easterEggButtonPressed)
forControlEvents:UIControlEventTouchUpInside];
self.easterEggButton = easterEggButton;
// NOTE that we add the Easter Egg Button to self.view over the Table View, not into Table View
[self.view addSubview:easterEggButton];
}
So we have the button placed over the tableView. This detaches it from interfering with tableView's gestureRecognizer.
Now you need some code to adjust the button's frame to be placed somewhere under the tableView's last cell. It may look something like this:
- (void)updateButtonFrame {
UIButton *button = self.easterEggButton;
// Just the easiest way calculate the width and height of the button depending on it's content
[button sizeToFit];
CGRect frame = button.bounds;
// Now, we want to place the button under all the cells
frame.origin.y = self.tableView.contentSize.height;
frame.origin.y += 60.0f; // add some padding to place it even lower
/* We have calculated button's frame in tableView's coordinate space, so we need to convert it to
* button superview's coordinate space. */
frame = [self.tableView convertRect:frame toView:button.superview];
// And finally apply the frame to the button
button.frame = frame;
}
And of course you need to call this layout method in appropriate moments, that is, whenever tableView reloads it's data (because contentSize may change), and whenever tableView scrolls.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
/* Button resides in self.view and is unaware of Table View scrolling, so we need to update
* it's frame whenever Table View's content offset changes */
[self updateButtonFrame];
}
You may also call it viewDidLayoutSubviews to be safe if tableView's cell height depends on screen orientation and such.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
// It's safe to recalculate button's frame whenever self.view layout updates
[self updateButtonFrame];
}
You may have a look at this example implementation
https://gist.github.com/bartekchlebek/429906e05fcbd976291d

Related

Pop up tutorial page like Photosynth's

I want to create something that is basically a clone of what photosynth does for their tutorial page. A small "?" button pops up what looks like a new view in a frame that is slightly smaller than the first view, so that you can still see the first view around the edges.
It's a little tough to see from the pic above, but the part around the edges is the old view that the tutorial display popped up over.
My first guess is that I need to use a container view somehow, but I can't find anything on the web about exactly how to do this. I can currently create a container view, hook it up to a new view controller via a segue, and do whatever I want in that new view controller, but the container view is always visible on the view it is contained within. Any help?
BTW, I'm using storyboarding with ARC.
You can add a transparent view to the key window, add a tap gesture recognizer that would dismiss it and the subviews to show the content:
#define OVERLAY_TAG 997
-(void)showTutorial
{
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView *overlay = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
overlay.backgroundColor = [UIColor clearColor];
overlay.userInteractionEnabled = YES;
[keyWindow addSubview:overlay];
UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(dismissTutorial)];
CGFloat border = 10;
CGRect frame = overlay.bounds;
// 20 is the status bar height (sorry for using the number)
frame = CGRectMake(border, border + 20, frame.size.width - border * 2, frame.size.height - border * 2 - 20);
// the black view in the example is probably a scroll view
UIView *blackView = [[UIView alloc] initWithFrame:frame];
blackView.backgroundColor = [UIColor blackColor];
blackView.alpha = 0.0;
[overlay addSubview:dimView];
// add all the subviews for your tutorial
// make it appear with an animation
[UIView animateWithDuration:0.3
animations:^{dimView.alpha = 1;}
completion:^(BOOL finished){[overlay addGestureRecognizer:tapRecognizer];}];
}
-(void)dismissTutorial
{
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView *overlay = [keyWindow viewWithTag:OVERLAY_TAG];
[UIView animateWithDuration:0.3
animations:^{
overlay.alpha = 0.0;
}
completion:^(BOOL finished){
[overlay removeFromSuperview];
}];
}
This way you would remove the tutorial with a simple tap but you can use a button for instance.

UIButton does not detect touch events when clicked

I've created a tableview that has custom cells (I've subclassed UITableViewCell).
I'm creating several UIButtons in it. Declaring this in the .h file:
#property (nonatomic, strong) UIButton *myButton;
and this in the .m file:
myButton = [[UIButton alloc] initWithFrame:CGRectMake(-100.0, 285.0, 60.0, 30.0)];
myButton.titleLabel.font = [UIFont boldSystemFontOfSize:16.0];
myButton.backgroundColor = [UIColor clearColor];
[myButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[myButton setTitle:#"text" forState:UIControlStateNormal];
[myButton addTarget:self action:#selector(myButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:myButton];
I'm moving the cell to the right using UIPanGestureRecognizer and then the UIButton is revealed (notice that it is in a negative X index).I can see it but it is not clickable (I guess it is not in the view any more).
Any suggestion about how to make it clickable?
More details:
In the init of the cell, I'm doing:
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)];
panGesture.delegate = self;
[self.contentView addGestureRecognizer:panGesture];
Then I implemented the gesture:
-(void)handlePanGesture:(UIPanGestureRecognizer *)sender {
CGPoint translate = [sender translationInView:self.contentView];
CGRect currentFrame = self.contentView.frame;
switch ([sender state]) {
case UIGestureRecognizerStateChanged:
currentFrame.origin.x = translate.x;
self.contentView.frame = currentFrame;
break;
default:
break;
}
}
When the frame moves to the right - the button is revealed. But not clickable...
If a button's frame is outside that of its parent's frame, it will not register touches. In this case, you mention that your button stops working when its frame's x location is set to -100. This makes sense because it is outside the touch area.
Your pan gesture you implemented moves the cell's view, so while the button may appear on screen, it is still OUTSIDE of the cell's view, hence it cannot receive touches.
One possible suggestion for fixing this would be to make your cells bigger from the start. So for example, if a cell's frame size is currently 0,0,320,44, you could instead make it -100,0,420,44 and place the contents of your cell accordingly. Now, your button's frame would change to 0,0,x,y and it would be inside of the cell's view and able to receive touches.

UIGestureRecognizer's sender view not working properly

According to this question, the UIGestureRecognizer has a view property which refers to the view the gesture is attached to. I used this in my code like this:
//Code for the 1st UIScrollView
UIImageView *bookCover = [[UIImageView alloc]initWithFrame:CGRectMake(50, 100, 145, 420)];
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc]initWithTarget:self
action:#selector(downloadBookTapped:)];
[bookCover addGestureRecognizer:singleTap];
[bookCover release];
[singleTap release];
//Code for the second UIScrollView
UIImageView *fileCover = [[UIImageView alloc]initWithFrame:CGRectMake(50, 100, 145, 420)];
UITapGestureRecognizer *singleFileTap = [[UITapGestureRecognizer alloc]initWithTarget:self
action:#selector(downloadFileTapped:)];
[fileCover addGestureRecognizer:singleFileTap];
[fileCover release];
[singleFileTap release];
And here is where I user the view property:
- (void)downloadBookTapped:(UITapGestureRecognizer *)sender
{
UIImageView *imgView = (UIImageView *)sender.view;
CGRect rect = [imgView frame];
UIImageView *images = [[UIImageView alloc]initWithFrame:rect];
//rest of code here...
}
- (void)downloadFileTapped:(UITapGestureRecognizer *)sender
{
UIImageView *imgView = (UIImageView *)sender.view;
CGRect rect = [imgView frame];
UIImageView *images = [[UIImageView alloc]initWithFrame:rect];
//rest of code here...
}
The problem here is that I have two scrollView and each scrollview holds multiple books. When I select a book at the 1st scrollView, the images is displayed correctly. But when I select a book inside the 2nd scrollView, the images is displayed incorrectly. Can anyone explain why this happens? Thanks.
---ADDITIONAL INFO---
The two scrollViews have the same width and height. The difference, of course, is there placement. The first scrollView is placed at (0, 0), while the second is at (0, 350). You can imagine the two as "shelves", the first one being the top shelf and the second one being the bottom shelf.
To specify the problem, say that I selected a book inside the second scrollView. The images will then be displayed as if I selected a book in the 1st scrollView. Meaning, the images is displayed in the 1st scrollView instead of the second scrollView.
Because the gestureRecognizer is bound to the first UIImageView and not the second.
[bookCover addGestureRecognizer:singleTap];
Do this for your other UIImageView and you will get the results you want.
I know now what I did wrong! Instead of adding the images as the subview of the scrollViews, I did this:
[self.view addSubView:images];
That's why It keeps appearing on the top side. It should be like this:
[scrollBook addSubview:images];
[scrollFile addSubView:files];

Capture dynamic button tap ios

I have button created programmatically in the UIImageView. This view was also created programmatically. When image in this view is tapped the button is created, now I want to capture when this button is tapped but I'm unable to here is my relevant code :
- (void)imageTapped:(UIGestureRecognizer *)sender{
MyImageView *myView = (MyImageView *)sender.view;
NSLog(#"Image tapped is => %#", myView.currentImageName);
//add button to view
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self
action:#selector(pressedPlay:)
forControlEvents:UIControlEventTouchDown];
//button image
UIImage *backgroundImage = [UIImage imageNamed:[NSString stringWithFormat:#"playbutton.png"]];
button.frame = CGRectMake(85.0, 175.0, backgroundImage.size.width, backgroundImage.size.height);
myView.userInteractionEnabled = YES;
button.userInteractionEnabled = YES;
[button setImage:backgroundImage forState:(UIControlStateNormal)];
[myView addSubview:button];
}
Now this code should handle the tap but for some reason it doesn't :
- (void)pressedPlay:(UIGestureRecognizer *)sender{
//MyImageView *senderView = (MyImageView *) sender.view;
NSLog(#"%#", #"I Presed button");
}
Any reason why this is not working?
Your code suggests that you are using a UIGestureRecognizer (most likely a UITapGestureRecognizer) to detect the initial tap. The problem is that UIGestureRecognizer is being greedy and stopping the UIButton from receiving the touch.
You will need to do some shifting to disable the gesture recognizer after the initial detection and then reenable it after the play has been tapped.
UIGestureRecognizer has the property
#property(nonatomic, getter=isEnabled) BOOL enabled
or you can choose to implement UIGestureRecognizerDelegate and provide an implementation of
gestureRecognizer:shouldReceiveTouch:
a very simple attempt could look like:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
{
return ![touch.view isKindOfClass:[UIButton class]];
}
and don't forget to set the delegate on the gesture recognizer
myGesture.delegate = self;
set the button frame from:
button.frame = CGRectMake(85.0, 175.0, backgroundImage.size.width, backgroundImage.size.height);
to:
button.frame = CGRectMake(0,0, backgroundImage.size.width, backgroundImage.size.height);
it looks like you are setting button frame outside the imageview's frame and hence the button is not actually getting any interaction.
The Button frame is likely setting the button outside the bounds of the UIImageView, frames are always relative to the superviews coordinate in this case the UIImageView.
[button setExclusiveTouch:YES];

How to show a UIDatePicker over the tab bar, in an orientation-friendly way?

I want to slide in a UIDatePicker when my user taps on my table view date field, exactly as in the standard contact app, when the user taps on the birthday field. With one additional killer detail:
It's in a tab bar application and I want the UIDatePicker to slide over the tab bar. And still rotates when the user puts her phone in the lanscape orientation.
The way I show the UIDatePicker is to insert in the view, and then animate its position:
[self.view addSubview: self.pickerView];
When I do this, the UIDatePicker is shown, but doesn't cover the tabbar.
I can also add it to the window:
[self.view.window addSubview: self.pickerView];
The UIDatePicker correctly slides over the tab bar, but then, it doesn't follow the orientation.
The only semi-acceptable way I found, is to do without the tab bar altogether, by using
detailViewController.hidesBottomBarWhenPushed = YES;
But it's not what I want: I want the tab bar in the detail view. I only want it to go away while picking the date. I can't put the UIDatePicker in its own controller with hidesBottomBarWhenPushed = YES as this would entirely cover the screen, and I'd rather have the detail view still partially visible.
Any suggestion welcome.
Here is the full code I use to show the UIDatePicker, copied over from some sample code:
- (void) doPickDate
{
NSDate *initialDateForPicker = [(ExpenseData*) self.displayedObject displayDate];
self.pickerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
self.pickerView.datePickerMode = UIDatePickerModeDate;
self.pickerView.date = initialDateForPicker;
// check if our date picker is already on screen
if (self.pickerView.superview == nil)
{
[self.view addSubview: self.pickerView];
// size up the picker view to our screen and compute the start/end frame origin for our slide up animation
// compute the start frame
CGRect screenRect = [[UIScreen mainScreen] applicationFrame];
CGSize pickerSize = [self.pickerView sizeThatFits:CGSizeZero];
CGRect startRect = CGRectMake(0.0,
screenRect.origin.y + screenRect.size.height,
pickerSize.width, pickerSize.height);
self.pickerView.frame = startRect;
// compute the end frame
CGRect pickerRect = CGRectMake(0.0,
screenRect.origin.y + screenRect.size.height - pickerSize.height,
pickerSize.width,
pickerSize.height);
// start the slide up animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
// we need to perform some post operations after the animation is complete
[UIView setAnimationDelegate:self];
self.pickerView.frame = pickerRect;
// shrink the table vertical size to make room for the date picker
CGRect newFrame = self.tableView.frame;
newFrame.size.height -= self.pickerView.frame.size.height - 49 /* tab bar height */;
self.tableView.frame = newFrame;
[UIView commitAnimations];
// add the "Done" button to the nav bar
self.navigationItem.rightBarButtonItem = self.doneButton;
}
}
You should set the UIDatePicker object as the inputView of that particular text field. All the animations and presentation stuff will be taken care of.
Hiding the cursor
There is no method to hide the cursor. The only mechanism I could think of was to use the leftView property and set it to a label.
CGRect frame = self.textField.bounds;
UILabel * theLabel = [[[UILabel alloc] initWithFrame:frame] autorelease];
theLabel.userInteractionEnabled = YES;
theLabel.backgroundColor = [UIColor clearColor];
UITapGestureRecognizer * tap = [[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(tap:)] autorelease];
[theLabel addGestureRecognizer:tap];
self.textField.leftViewMode = UITextFieldViewModeAlways;
self.textField.leftView = theLabel;
self.textField.clipsToBounds = YES;
and handle the tap using,
- (void)tap:(UIGestureRecognizer *)gesture {
[self.textField becomeFirstResponder];
}
Now this doesn't work for the rounded rect button as the cursor blinks on the right corner no matter what the label's frame is. It hides the cursor for other border styles. You can also use the label to your advantage by setting your values as its text.
UILabel * theLabel = textField.leftView;
theLabel.text = #"Appropriate Value from Picker";