I have an application which has two views on one view controller. One view is above the other. When someone swipes or presses a button, the view on top moves to the side to display the bottom view. When someone opens a new view and wants to go back to the view controller with the two views, I want to have the view on top to reveal the bottom view automatically.
This is my code:
#interface ViewController ()
#end
#implementation ViewController
#synthesize topLayer = _topLayer;
#synthesize layerPosition = _layerPosition;
- (void)viewDidLoad
{
[super viewDidLoad]
self.topLayer.layer.shadowOffset = CGSizeMake(-1,0);
self.topLayer.layer.shadowOpacity = .9;
self.layerPosition = self.topLayer.frame.origin.x;
}
#define VIEW_HIDDEN 264
-(void) animateLayerToPoint:(CGFloat)x
{
[UIView animateWithDuration:0.3
delay:0
options:UIViewAnimationCurveEaseOut
animations:^{
CGRect frame = self.topLayer.frame;
frame.origin.x = x;
self.topLayer.frame = frame;
}
completion:^(BOOL finished){
self.layerPosition =self.topLayer.frame.origin.x;
}];
}
- (IBAction)toggleLayer:(id)sender {
if (self.layerPosition == VIEW_HIDDEN) {
[self animateLayerToPoint:0];
} else {
[self animateLayerToPoint:VIEW_HIDDEN];
}
}
- (IBAction)panLayer:(UIPanGestureRecognizer*)pan {
if (pan.state == UIGestureRecognizerStateChanged) {
CGPoint point = [pan translationInView:self.topLayer];
CGRect frame = self.topLayer.frame;
frame.origin.x = self.layerPosition + point.x;
if (frame.origin.x < 0 ) frame.origin.x = 0;
self.topLayer.frame = frame;
}
if (pan.state == UIGestureRecognizerStateEnded) {
if (self.topLayer.frame.origin.x <= 160) {
[self animateLayerToPoint:0];
} else {
[self animateLayerToPoint: VIEW_HIDDEN];
}
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Override -viewWillAppear to arrange the views however you want them based on the state of the app. If the user hasn't swiped or whatever, put the first view on top. If they have swiped or whatever, move the view to the side.
-viewDidLoad is the right place to perform any setup that needs to happen when the view is first loaded. -viewWillAppear is the place to do things that should happen just before the view controller's view is displayed, and it's called every time that happens.
Related
I have a Custom Transition, I apply it in a ViewController which complies with UIViewControllerTransitioningDelegate protocol. My transition works ok. But when i change UIViewControllerTransitioningDelegate protocol to UINavigationControllerDelegate protocol, it works strangely.
The function i want to realize
Strange presentation with only one piece of toView
MyTransitioning:
#import "MyTransitioning.h"
#define PARTS_COUNT 4
#interface MyTransitioning()
#property (nonatomic) CGFloat partWidth;
#property (nonatomic) CGFloat partHeight;
#end
#implementation MyTransitioning
-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 1.0;
}
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* fromCtrl = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController* toCtrl = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView* fromView = fromCtrl.view;
UIView* toView = toCtrl.view;
UIView* container = [transitionContext containerView];
CATransform3D transform = container.layer.transform;
transform.m34 = -0.01;
[container.layer setSublayerTransform:transform];
NSArray<UIView*>* fromViewParts = [self spliteView:fromView];
NSArray<UIView*>* toViewParts = [self spliteView:toView];
[self addViews:toViewParts ToContainer:container isFromView:NO];
[self addViews:fromViewParts ToContainer:container isFromView:YES];
[fromView removeFromSuperview];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^
{
[self strechViews:toViewParts];
[self compressViews:fromViewParts ToLeft:YES];
} completion:^(BOOL finished)
{
if (!finished)
{
NSLog(#"not finish");
}
[self removeViewsfromSuper:fromViewParts];
[self removeViewsfromSuper:toViewParts];
[container addSubview:toView];
[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
}];
}
-(NSArray*)spliteView:(UIView*)view
{
self.partWidth = view.bounds.size.width / PARTS_COUNT;
self.partHeight = view.bounds.size.height;
CGRect region = CGRectMake(0, 0, self.partWidth, self.partHeight);
NSMutableArray<UIView*>* viewParts = [NSMutableArray array];
for (int i = 0; i < PARTS_COUNT; i++)
{
region.origin.x =self.partWidth * i;
UIView* partView = [view resizableSnapshotViewFromRect:region afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];
[viewParts addObject:partView];
partView.frame = region;
}
return viewParts;
}
-(void)addViews:(NSArray<UIView*>*)views ToContainer:(UIView*)container isFromView:(BOOL)isFromView
{
isFromView ? [self strechViews:views] : [self compressViews:views ToLeft:NO];
for (UIView* view in views)
{
[container addSubview:view];
}
}
-(void)compressViews:(NSArray<UIView*>*)views ToLeft:(BOOL)toLeft
{
CGFloat x = toLeft ? 0 : self.partWidth * 4;
int isClockWise = -1;
CGPoint position = CGPointMake(x, self.partHeight / 2);
for (UIView* view in views) {
isClockWise *= -1;
view.layer.anchorPoint = CGPointMake(0.5 - isClockWise * 0.5, 0.5);
view.layer.position = position;
view.layer.transform = CATransform3DMakeRotation(M_PI_2 * isClockWise, 0, 1, 0);
}
}
-(void)strechViews:(NSArray<UIView*>*)views
{
int isClockWise = -1;
for (UIView* view in views)
{
isClockWise *= -1;
view.layer.anchorPoint = CGPointMake(0.5 - isClockWise * 0.5, 0.5);
NSLog(#"%#",NSStringFromCGPoint(view.layer.anchorPoint));
CGPoint position = view.layer.position;
position.x = self.partWidth * [views indexOfObject:view] + (isClockWise == 1 ? 0 : self.partWidth);
view.layer.position = position;
view.layer.transform = CATransform3DIdentity;
NSLog(#"%#",NSStringFromCGRect(view.frame));
}
}
-(void)removeViewsfromSuper:(NSArray<UIView*>*)views
{
for (UIView* view in views)
{
[view removeFromSuperview];
}
}
#end
ViewController:
#import "ViewController.h"
#import "MyTransitioning.h"
#import "SecondController.h"
#interface ViewController ()<UINavigationControllerDelegate>
//#interface ViewController ()<UIViewControllerTransitioningDelegate>
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
//-(id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
//{
// return [MyTransitioning new];
//}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
self.navigationController.delegate = self;
// SecondController* ctrl = [segue destinationViewController];
// ctrl.transitioningDelegate = self;
}
-(id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
if (operation == UINavigationControllerOperationPush) {
return [MyTransitioning new];
}
return nil;
}
#end
If your question is difference between PresentViewController and Push,pop view controller then,
PushViewController
Push View controller is a property of the Navigation controller. When you want to move forward from one controller to other you use this.
PopViewController
Pop view controller is also a property of the navigation controller. When you want to go backward in navigation stack you this.
PresentViewController / Model View controller
Present view controller is not a property of the navigation controller. To perform either push/pop your controller should be subclassed with UINavigationController either directly or indirectly. Whereas to present view controller you don't need to have reference of navigation controller, directly you call [self presentViewController..]
I have two view controllers, one is the home view, one is the side menu view; all of the objects and created with storyboard.
there is a bar button 'item' on home view, when clicking it, it will invoke a custom segue, to slide out the side menu from left to right. So far so good.
On side menu, there is a button to slide back to home view. However, I simply click the button, the app will terminated, Xcode does not print out the crash log overtime.
I tried to add a breakpoint on (IBAction)SlideBack:(id)sender, seems like it did not get invoked. So something series happened.
Xcode break at
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
and saying Thread 1: exc_bad_access(code=EXC_i386_GPFLT)
I suspect the custom segue may interfere the view and view controllers hierarchy. But could not find out.
home view implementation:
#implementation HomeViewController
#end
side bar implementation:
#implementation SideBarController
- (IBAction)SlideBack:(id)sender {
NSLog(#"sliding back");
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
The custom slide out segue is:
#implementation CustomSegue
- (void)perform {
UIViewController *contentVC = self.sourceViewController;
UIViewController *sideBarVC = self.destinationViewController;
CGRect sideFrame = sideBarVC.view.frame;
[sideBarVC.view setFrame: CGRectMake(-(contentVC.view.frame.size.width),
0,
contentVC.view.frame.size.width/2,
contentVC.view.frame.size.height)];
CGRect animationFrame = contentVC.view.frame;
sideFrame = sideBarVC.view.frame;
animationFrame.size.width = contentVC.view.frame.size.width / 2;
sideBarVC.view.alpha = 0;
[[[contentVC.view superview] window] addSubview:sideBarVC.view];
[UIView animateWithDuration:1
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
sideBarVC.view.frame = animationFrame;
sideBarVC.view.alpha = 1;
}
completion:^(BOOL finished) {
sideBarVC.view.alpha = 1;
sideBarVC.view.frame = animationFrame;
}];
}
After seeking helps, I found I misunderstand the segue logic.
[[[contentVC.view superview] window] addSubview:sideBarVC.view]; is not enough.
Instead, below code should be right:
[contentVC addChildViewController:sideBarVC];
[[[contentVC.view superview] window] addSubview:sideBarVC.view]
[sideBarVC didMoveToParentViewController:contengVC];
I need to move an UIImageView from x,y to x,y1.
This is what i have. I tried lots of different codes in -(void)animation, most using animateWithDuration but none seems to work. It just doesn't do anything. I must be missing something. Is .h correct? How would you make the image move? I then have to loop that, how can i do that? It's my first time with animate, I can't figure out what to do, it has to be simple. Thanks in Advance.
in .h I connected it this way
#import <UIKit/UIKit.h>
#interface ImageBeaconViewController : UIViewController {
__weak IBOutlet UIImageView *blueView;
}
#end
the in .m I've got
- (void)viewDidLoad
{
[self animation];
[super viewDidLoad];
}
....
- (void)animation{
[UIView animateWithDuration:3 animations:^{
blueView.alpha = 1;
blueView.center = CGPointMake(blueView.center.x , blueView.center.y +??);
}];
}
??= I still have to decide how much I want it to move
This way the app crashes as soon as I get to the screen with the animation
Add Method Like This:
- (void) animateToPoint:(CGPoint)point {
[UIView animateWithDuration:3 animations:^{
blueView.alpha = 1;
blueView.center = point;
}];
}
Call like this:
- (void) viewDidAppear:(BOOL)animated {
int distanceToSlide = 40; // will slide down 40px (use -40 to go up)
CGPoint targetPoint = CGPointMake(blueView.center.x, blueView.center.y + distanceToSlide);
[self animateToPoint:targetPoint];
}
NOTE
Notice that I put your animation call in viewDidAppear if you animate in viewDidLoad you won't see anything because your view isn't displayed yet.
If it's still not moving, make sure "useAutolayout" is not selected check this:
Just because I already built it, try this for tap to animate:
In your viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc]init];
tap.numberOfTapsRequired = 1;
[tap addTarget:self action:#selector(handleTap:)];
[self.view addGestureRecognizer:tap];
}
Then, use these:
- (void) handleTap:(UITapGestureRecognizer *)tap {
int distanceToSlide = 40; // will slide down 40px (use -40 to go up)
CGPoint targetPoint = CGPointMake(blueView.center.x, blueView.center.y + distanceToSlide);
[self animateToPoint:targetPoint];
}
- (void) animateToPoint:(CGPoint)point {
[UIView animateWithDuration:3 animations:^{
blueView.alpha = 1;
blueView.center = point;
}];
}
Here's a way to loop it:
#implementation ImageBeaconViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
pointA = CGPointMake(40, 40);
pointB = CGPointMake(250, 350);
blueView.center = pointA;
}
- (void) viewDidAppear:(BOOL)animated {
[self animate];
}
- (void) animate {
if (CGPointEqualToPoint(_blueView.center, pointA)) {
[self animateToPoint:pointB];
}
else {
[self animateToPoint:pointA];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void) animateToPoint:(CGPoint)point {
[UIView animateWithDuration:1.0 animations:^{
_blueView.alpha = 1;
_blueView.center = point;
} completion:^(BOOL finished) {
if (finished) {
[self animate];
}
}];
}
#end
[UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
blueView.alpha = 1;
blueView.center = CGPointMake(blueView.center.x , blueView.center.y + 100);
} completion:^(BOOL finished) {
//if you need any thing after animation is done
}];
this is them code for animation i have
Here's the setup: I have UIControls in table cells that allow sliding from right to left for a delete function (like Clear)
The table cells live inside a UITableView.
The TableView lives inside another UIControl that allows swiping from left to right or right to left in order to change days. When this happens a new TableView gets created to the right or left of the main one, and the new one is pulled in from left or right until a threshold is met and an animation takes over and then replaces the old with the new.
In some conditions all of these interactions actually work correctly. The issue is that after a couple of interactions (table slides seem to be problematic) it becomes difficult / impossible to scroll the table.
Here is the code for the TableViewSlider (the top level UIControl that contains the TableViews). Any ideas would be greatly appreciated!
#implementation OSETableViewSlider
- (void) initialize {
self.backgroundColor = [UIColor backgroundFlatColor];
self.mainTableView = [self createUITableView];
self.mainTableView.frame = CGRectMake(0,0, self.frame.size.width, self.frame.size.height);
self.mainTableView.hidden = NO;
[self addSubview:self.mainTableView];
self.transitionInProgress = NO;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initialize];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if(self) {
[self initialize];
}
return self;
}
- (UITableView *)createUITableView {
UITableView *newTable = [[UITableView alloc] init];
newTable.hidden = YES;
newTable.separatorStyle = UITableViewCellSeparatorStyleNone;
newTable.scrollEnabled = YES;
newTable.bounces = YES;
newTable.dataSource = self;
newTable.delegate = self;
newTable.backgroundColor = [UIColor backgroundFlatColor];
return newTable;
}
- (void)setFrame:(CGRect)frame {
super.frame = frame;
self.mainTableView.frame = CGRectMake(0,0, frame.size.width, frame.size.height);
}
- (void)transitionToDate:(NSDate *)date fromRight:(BOOL)rightToLeft {
[self.viewController beginTransitionToDate:date];
self.transitionTableView = [self createUITableView];
self.transitionTableView.frame = CGRectMake( rightToLeft ? self.frame.size.width : (-1 * self.frame.size.width), 0,
self.frame.size.width, self.frame.size.height );
self.transitionTableView.hidden = NO;
[self addSubview:self.transitionTableView];
self.transitionInProgress = YES;
[UIView animateWithDuration:0.2f animations:^{
self.transitionTableView.frame = CGRectMake(0,0, self.frame.size.width, self.frame.size.height);
self.mainTableView.frame = CGRectMake( rightToLeft ? (-1 * self.frame.size.width) : self.frame.size.width, 0,
self.frame.size.width, self.frame.size.height);
} completion:^(BOOL completed){
[self.viewController endTransitionDidChange:YES];
[self.mainTableView removeFromSuperview];
self.mainTableView = self.transitionTableView;
self.transitionInProgress = NO;
}];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.viewController activityCountForTransition:(tableView!=self.mainTableView)];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self.viewController cellNumber:indexPath.item forTransition:(tableView!=self.mainTableView)];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self.viewController cellNumber:indexPath.item forTransition:(tableView!=self.mainTableView)].frame.size.height;
}
- (void)layoutSubviews {
if(!self.transitionInProgress) {
[self.mainTableView setFrame:CGRectMake(0,0, self.frame.size.width, self.frame.size.height)];
}
}
- (BOOL) beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
NSLog(#"begin track");
self.locationInView = [touch locationInView:self.superview];
self.fingerTracking = YES;
self.fingerMoved = NO;
self.transitionTableView = nil;
return YES;
}
- (CGFloat) calcOffsetForTouch:(UITouch *)touch {
CGFloat fingerOffset = [touch locationInView:self.superview].x - self.locationInView.x;
return fingerOffset + self.startingOffset;
}
- (BOOL) continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
self.fingerMoved = YES;
CGFloat offset = [self calcOffsetForTouch:touch];
//NSLog(#"offset is: %f flarb: %f", offset, fabs(offset));
if(offset < 0 && (!self.transitioningLeft || [OSEUtils isNull:self.transitionTableView])) {
[self.viewController beginTransitionToDate:[OSEUtils daysOffset:1 fromDate:self.viewController.selectedDate withTimeZone:[NSTimeZone timeZoneWithName:#"UTC"]]];
[self.transitionTableView removeFromSuperview];
self.transitionTableView = [self createUITableView];
self.transitioningLeft = YES;
NSLog(#"going left");
[self addSubview:self.transitionTableView];
[self.transitionTableView reloadData];
}
if(offset > 0 && (self.transitioningLeft || [OSEUtils isNull:self.transitionTableView])) {
[self.viewController beginTransitionToDate:[OSEUtils daysOffset:-1 fromDate:self.viewController.selectedDate withTimeZone:[NSTimeZone timeZoneWithName:#"UTC"]]];
[self.transitionTableView removeFromSuperview];
self.transitionTableView = [self createUITableView];
self.transitioningLeft = NO;
NSLog(#"going right");
[self addSubview:self.transitionTableView];
[self.transitionTableView reloadData];
}
CGFloat mult = self.transitioningLeft ? 1 : -1;
if(fabs(offset) > (self.frame.size.width / 3.0f)) {
if(self.transitioningLeft) {
offset = -1 * self.frame.size.width;
} else {
offset = self.frame.size.width;
}
[UIView animateWithDuration:0.2f animations:^{
self.mainTableView.frame = CGRectMake(offset, 0, self.frame.size.width, self.frame.size.height);
self.transitionTableView.frame = CGRectMake(offset + (mult * self.frame.size.width), 0,
self.frame.size.width, self.frame.size.height);
} completion:^(BOOL finished) {
[self.mainTableView removeFromSuperview];
if(self.transitioningLeft) {
[self.viewController.daySelector jumpToNext];
} else {
[self.viewController.daySelector jumpToPrevious];
}
self.mainTableView = self.transitionTableView;
self.transitionTableView = nil;
[self.viewController endTransitionDidChange:YES];
}];
return NO;
}
self.mainTableView.frame = CGRectMake(offset, 0, self.frame.size.width, self.frame.size.height);
self.transitionTableView.frame = CGRectMake(offset + (mult * self.frame.size.width), 0, self.frame.size.width, self.frame.size.height);
self.transitionTableView.hidden = NO;
return YES;
}
- (void) endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
NSLog(#"end tableslide track");
[UIView animateWithDuration:0.2f animations:^{
self.mainTableView.frame = CGRectMake(0,0,self.frame.size.width, self.frame.size.height);
self.transitionTableView.frame = CGRectMake(((self.transitioningLeft ? 1 : -1 ) * self.frame.size.width), 0, self.frame.size.width, self.frame.size.height);
} completion:^(BOOL completion){
[self.transitionTableView removeFromSuperview];
self.transitionTableView = nil;
[self.viewController endTransitionDidChange:NO];
}];
self.fingerTracking = NO;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *tableHitTest = [self.mainTableView hitTest:point withEvent:event];
if([tableHitTest isKindOfClass:[OSECellPanelControl class]]) {
return tableHitTest;
} else {
return self;
}
}
#end
Why are you creating a new UITableView when the user swipes left or right? It would be much more efficient to use the built-in deleteRowsAtIndexPath: and insertRowsAtIndexPath: methods to animate the rows sliding in and out without having to slide in and out entire UITableView's.
You can take a look at this for a reference on animating the tableview content changes as you swipe across dates.
I don't know about your current set up, but when I tried this exact same thing with swipeable UITableViewCell's and it's superview being swipe able, I had issues with getting the interaction that I wanted. If this is contributing to your problem, you can look at detecting where the user swipes. If the user swipes within a specific offset from the left and right edge of the UITableViewCell, then it swipes the cell, anything outside of that offset (center of the cell) would have the UITableView itself swiped. You would then animate out the deleting and insertion of the new rows.
Also, by re-instancing a new UITableView each time, you are forcing your UITableViewCell's to be re-instanced each time as well. By using the delete and insert methods, you can continue to use your existing cells via the dequeueReusableCellWithIdentifier method. Each time you re-instance the UITableView, your cells are set to nil and discarded requiring the new tableview to create complete new instances of your cells. This is extremely inefficient.
You're getting in deep with touch tracking and hit detection. Use a higher level API instead - gesture recognisers. With gestures you can set precedence (this one has to fail before this one can begin).
So, you want a couple of custom gestures on the container view (which no longer needs to be a control). These fail if the initial touch point isn't at the edge of the view (within some bound). These trigger the overall table view change.
And the table cells have swipe gestures that each require the custom ones to fail. These trigger your cell deletion logic.
Your idea share a few similarities with how the iOS7 unlock screen and calendar is built. In the unlock screen, you can swipe on notifications, move the notification list up and down, and swipe to unlock. In the calendar app, you can swipe to change the current day, or swipe the header to change the week. Thus I recommend watching the WWDC 2013 Session video #217 "Exploring Scroll Views on iOS 7" that explain and demonstrate how they have done it, and how to replicate the functionality.
Basically, what you could do (and what they did for the unlock screen and calendar screen) is: Either (A) nest UIScrollViews in a UITableView OR (B) use a swipe/pan gesture delegate on every cells, and (C) nest that UITableView along with every other UITableViews representing days in a UIScrollView with paging control enabled.
(A) will allow you to expose a delete button in cells. You could either put the delete button under the UIScrollView that sits in the cell in a way that sliding the content uncover it.
(B) will allow you to track a gesture in cells, cancel it if required, and play with the cell's contentView to display a delete button.
(C) will allow you to swipe from one UITableView to another quite easily, and with paging enable you will get a free "snap into place or bounce back" animation PLUS you can easily reuse two UITableViews instead of allocating one per day.
Personally, I would go with (B) and (C).
Turns out there was a problem with my hitTest method. Replacing [self.mainTableView hitTest...] with [super hitTest...] solved my problem. I think I was messing up some coordinates that was causing this method to return nil when it should have returned a view. I'm not 100% sure exactly what is wrong with it, but it does work better now.
I have a requirement to show a status bar at certain times at the bottom of my application. I can easily put this at the bottom of my application's main view, but whenever I push a view controller on top of this (either modally or not) it hides this status bar.
Is there any way I can add a status bar like this, and have it be outside the bounds of my application itself? Ideally I'd like this to work like the call-in-progress status bar on the iPhone - when this bar appears, the app is pushed down, and a call to [[UIScreen mainScreen] applicationFrame] returns the correct size (i.e. it accounts for the presence of this status bar when calculating the height available for the app).
I wanted to do this, too, so I tried View Controller Containment. I'm still trying it out, so I'm not willing to give this a ringing endorsement, but it might be something you'd want to try playing around with yourself if you're in iOS5. But it appears to give you a status bar that will appear or disappear from the bottom of the screen.
This is a view controller that will open another view controller, but if there is status text to show, it pops up from the bottom of the screen and stays there until you get rid of it. I've only done a little testing so far, but it looks like this handles pushViewController/popViewController, but maybe not modal views.
My header looks like:
// StatusBarViewController.h
//
// Created by Robert Ryan on 7/8/12.
#import <UIKit/UIKit.h>
#interface StatusBarViewController : UIViewController
#property (strong, nonatomic) UIViewController *appController;
- (void)setStatus:(NSString *)text;
#end
My implementation file (this is ARC) looks like:
// StatusBarViewController.m
//
// Created by Robert Ryan on 7/8/12.
#import "StatusBarViewController.h"
#interface StatusBarViewController ()
{
BOOL _statusHidden;
UIView *_appView;
UILabel *_statusLabel;
}
#end
#implementation StatusBarViewController
#synthesize appController = _appController;
- (void)dealloc
{
_appView = nil;
_statusLabel = nil;
[self setAppController:nil]; // usually I don't like setters in dealloc, but this does some special stuff
}
- (void)createControlsWithStatusHidden
{
// create default app view that takes up whole screen
CGRect frame = self.view.frame;
frame.origin = CGPointMake(0.0, 0.0);
_appView = [[UIView alloc] initWithFrame:frame];
_appView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_appView.clipsToBounds = YES;
[self.view addSubview:_appView];
// create status label that is just off screen below the app view
_statusLabel = [[UILabel alloc] init];
_statusLabel.font = [UIFont fontWithName:#"Helvetica-Bold" size:12.0];
_statusLabel.backgroundColor = [UIColor darkGrayColor];
_statusLabel.textColor = [UIColor whiteColor];
CGSize size = [#"Hey!" sizeWithFont:_statusLabel.font]; // test size of box with random text
_statusLabel.frame = CGRectMake(0.0, frame.size.height, frame.size.width, size.height);
_statusLabel.textAlignment = UITextAlignmentCenter;
_statusLabel.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
[self.view addSubview:_statusLabel];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self createControlsWithStatusHidden];
_statusHidden = YES;
// I'm instantiating from storyboard. If you're using NIBs, just create your controller controller using initWithNib and then set our appController accordingly.
self.appController = [self.storyboard instantiateViewControllerWithIdentifier:#"MainNavigator"];
}
- (void)setAppController:(UIViewController *)controller
{
if (controller)
{
controller.view.frame = CGRectMake(0.0, 0.0, _appView.frame.size.width, _appView.frame.size.height);
[self addChildViewController:controller];
[controller didMoveToParentViewController:self];
if (self.appController)
{
// if we have both a new controller and and old one, then let's transition, cleaning up the old one upon completion
[self transitionFromViewController:self.appController
toViewController:controller
duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionCurveEaseInOut
animations:nil
completion:^(BOOL finished){
if (self.appController)
{
[self.appController willMoveToParentViewController:nil];
[self.appController removeFromParentViewController];
}
}];
}
else
{
// if we have no previous controller (i.e. this is our first rodeo), then just add it to the view
[_appView addSubview:controller.view];
}
}
else
{
// no new controller, so we're just removing any old on if it was there
if (self.appController)
{
// if there was an old controller, remove it's view, and remove it from the view controller hierarchy
[self.appController.view removeFromSuperview];
[self.appController willMoveToParentViewController:nil];
[self.appController removeFromParentViewController];
}
}
_appController = controller;
}
- (void)hideStatusWithCompletion:(void (^)(BOOL finished))completion
{
[UIView animateWithDuration:0.25
animations:^{
CGRect labelFrame = _statusLabel.frame;
labelFrame.origin.y += labelFrame.size.height;
_statusLabel.frame = labelFrame;
CGRect appFrame = _appView.frame;
appFrame.size.height += labelFrame.size.height;
_appView.frame = appFrame;
}
completion:completion];
}
- (void)unhideStatusWithCompletion:(void (^)(BOOL finished))completion
{
[UIView animateWithDuration:0.25
animations:^{
CGRect labelFrame = _statusLabel.frame;
labelFrame.origin.y -= labelFrame.size.height;
_statusLabel.frame = labelFrame;
CGRect appFrame = _appView.frame;
appFrame.size.height -= labelFrame.size.height;
_appView.frame = appFrame;
}
completion:completion];
}
- (void)setStatus:(NSString *)text
{
BOOL hasText = (text && [text length] > 0);
if (hasText)
{
if (!_statusHidden)
{
// if we have text, but status is already shown, then hide it and unhide it with new value
[self hideStatusWithCompletion:^(BOOL finished){
_statusLabel.text = text;
[self unhideStatusWithCompletion:nil];
}];
}
else
{
// if we have text, but no status is currently shown, then just unhide it
_statusLabel.text = text;
[self unhideStatusWithCompletion:nil];
}
_statusHidden = NO;
}
else
{
if (!_statusHidden)
{
// if we don't have text, but status bar is shown, then just hide it
[self hideStatusWithCompletion:^(BOOL finished){
_statusLabel.text = text;
}];
_statusHidden = YES;
}
}
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#end
And then, any view controller that wants to update the status message would use a method kind of like:
- (void)setStatus:(NSString *)text
{
UIViewController *controller = [UIApplication sharedApplication].delegate.window.rootViewController;
if ([controller isKindOfClass:[StatusBarViewController class]])
{
[(StatusBarViewController *)controller setStatus:text];
}
}