Animate UITextview change out, Keep keyboard in place - ios

I have a UITextview, becomes first responder and keyboard is presented. Presently, I have buttons in inputAccessoryView toolbar that exchange the text forward and reverse through an array of strings.
I have the process working with no issues, so of course I am inclined to break it. My wish is to slide the textview left and right like a carousel to make it more clear to the user that next or previous string is coming and going. The current system simply replaces the text with no animation.
My first thought was to create a UINavigationController, give it an array of UIViewControllers that present the UITextviews. The navigation controllers view is only as big as the textview and I add it as a subview to my full view (which is itself in a navigation controller). I got this working fairly completely, the navigation bar is hidden and it looks no different than the original textview except that the textview now slides off to the right or left, depending if I am pushing or popping.
The problem with that is that the keyboard slides off along with the dismissed view controller, then the new textview in the new controller becomes first responder and the keyboard returns. Close, but no cigar.
I considered using page view controller but it seems it will have the same issue. I think I may have to go back to the single textview and animate the whole process directly with static screen grabs. That is completely beyond my experience level and I am think there must be a simpler way.
Can anyone suggest a simple way to keep that keyboard present while the views are swapped as described? Suggestions on other angles of attacking this?

Seems like an awful lot of overhead for a simple animation.
Try something like this (assuming ARC):
typedef enum _eDirection
{
rightToLeft = -1,
leftToRight = 1
} eDirection;
- (void) animateTextFields:(eDirection)direction
{
CGFloat distance = self.window.bounds.size.width;
UITextField *oldTextField = thisView.textField;
CGRect oldTFRect = oldTextField.frame;
CGRect newTFRect = CGRectOffset(oldTFRect, -direction * distance, 0);
UITextField *newTextField = [[UITextField alloc] initWithFrame:newTFRect];
[newTextField setText:#"whatever"];
[self addSubview:newTextField];
[UIView animateWithDuration:0.3f
animations:^{
[oldTextField setFrame:CGRectOffset(oldTextField.frame, direction * distance, 0)];
[newTextField setFrame:CGRectOffset(newTextField.frame, direction * distance, 0)];
} completion:^(BOOL finished) {
[self setTextField:newTextField];
[self removeSubview:oldTextField];
[newTextField becomeFirstResponder];
}];
}
(Disclaimer: as with all code typed off the top of one's head, etc., etc.)
This method would create a new textField, animate it horizontally onto the screen while moving the existing one off the screen in the direction you give, and then assigns the new textField as the current one and makes it the firstResponder.

Related

Synchronise animation with top of a keyboard with accessory view

I have a UITextField which has an accessory view. I also have another view which resides at the bottom of the screen. I want this view to animate up as the keyboard shows, which I have done plenty of times by utilizing the UIKeyboardWillShowNotification. However, this time it is a little special since I want the bottom view to follow the top of the keyboard and not the top of the accessory view.
Obviously I have tried this solution:
[UIView animateWithDuration:keyboardAnimationDuration delay:0 options:keyboardAnimationCurve animations:^{
viewBottomConstraint.constant = keyboardHeight - accessoryViewHeight;
[self.overviewViewController.view layoutIfNeeded];
} completion:nil];
(keyboardAnimationDuration, keyboardAnimationCurve and keyboardHeight all come from the userInfo dictionary of the keyboard notification)
This solution gives me the correct end position of the bottom view, but the animation is slightly off since the keyboard (including the accessory view) has to traverse a longer distance in the same duration (to the top of the accessory view instead of to the top of the keyboard).
I have considered calculating a relative animation duration for my bottom view like this:
keyboardToAccessoryRatio = (keyboardHeight - accessoryViewHeight) / keyboardHeight
relativeDuration = keyboardAnimationDuration * keyboardToAccessoryRatio
And then have the animation start with a delay of keyboardAnimationDuration - relativeDuration. However, this would only work nicely if the keyboard was using the UIViewAnimationOptionCurveLinear curve, but it is, in fact, using UIViewAnimationCurveEaseInOut.
So any other suggestions?
PS. I cannot simply add my button view to the accessory view as they are actually in separate view controllers. The animation is done as a part of a custom transition within a UIViewControllerAnimatedTransitioning class.
In the end I gave up and added the accessory view as a regular view which I made follow the keyboard.

iMessage Extension: UITextView in Child View Controller, scrolling behaviour has multiple issues

I have a very basic view controller in an iMessage Extension. (Expanded view of course). I am aware that you can not have keyboard input int the Compact view...
The UITextView is constrained to the Element above it, and below it. It has a set height and width. The constraints are all in order, and are not broken.
All the options on the textview in interface builder have been left as DEFAULTS.
The following problems occur with the UITextView:
When I begin editing, the cursor is barely visible. (on the bottom of the field, half way cut off vertically).
Typing the first letter brings the cursor to the vertical centre of the Text View.
Putting a new line and then a character, puts the cursor back down again.
Then typing subsequent characters after that will bring the cursor to the Vertical Centre again. And every other character scrolls the whole text view to the bottom again. So you get this weird bounce up and down behaviour with every key stroke.
The Scroll Bar visual hint on the right side does not reflect the proper height of the text view, when I scroll. (The scroll bar only goes down half way - of the text view's height.)
I expect the UITextView to work like this by default:
Cursor should go to the TOP of the text view, when activated and editing.
Text View should not jump when typing keys after a new line.
Vertical Scroll Bar indicator should show the full range of the text field, and not half of it.
So, obviously the internal Scroll View inside the Text View has a problem figuring out the sizes of things.
Does anyone know, if this is an iMessage specific issue, or whether anyone has had this problem outside of iMessage as well, and how to fix?
My IB constraints (The second view down is the text view):
Video Demo of the problem:
https://youtu.be/1bkvHnkXLWM
UPDATE: I am using Child View Controllers to embed my View Controller inside the Root Controller. I have tried a blank Hello World application, and the UITextView works normally by default. So the issue, therefore is related to the way i'm embedding a Child View Controller.
This is the code I'm using to embed my Child View Controller:
- (void)showViewController:(UIViewController*)vcToShow isPop:(BOOL)isPop
{
NSLog(#"Presenting Controller: %#", vcToShow);
[self.activeVC willMoveToParentViewController:nil];
[self addChildViewController:vcToShow];
vcToShow.view.translatesAutoresizingMaskIntoConstraints = NO;
vcToShow.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.rootView addControls:#[vcToShow.view]
align:VerticalAlignStretchToFullHeight];
UIViewController* currentVC = self.activeVC;
self.activeVC = vcToShow;
vcToShow.view.alpha = 0.0f;
[self transitionFromViewController:currentVC
toViewController:vcToShow
duration:0.3
options:UIViewAnimationOptionTransitionNone
animations:^{
vcToShow.view.alpha = 1.0f;
}
completion:^(BOOL finished) {
[currentVC removeFromParentViewController];
[vcToShow didMoveToParentViewController:self];
[self updateScreenState];
if (isPop) {
[self removeReferenceToController:currentVC];
}
}];
}
I have a solution.
Instead of using Interface Builder to place my UITextView, I used my own programmatic constraint generation method to create and place the UITextView, only after ViewDidAppear.
My suspicion is that at the time Interface builder rendered the Child VC's view, it did not have everything populated that the UITextView's internal scroll view needed. So the scroll view got initialized with wrong dimension values, and therefore would glitch out on anything scrolling related. After doing everything in viewDidAppear programmatically (in the Child VC), the text view now scrolls properly, and the cursor is always at the beginning, working as expected.
The update code:
Took UITextView out of interface builder completely.
Added it programmatically (called from viewDidAppear):
self.textView = [[UITextView alloc] init];
self.textView.translatesAutoresizingMaskIntoConstraints = NO;
CGFloat topPadding = self.instructionLabel.frame.origin.y + self.instructionLabel.frame.size.height + TEXT_VIEW_TOP_PADDING;
// This custom method generates Visual Constraint format for me
// so i don't have to write manual individual constraint syntax.
[self.rootView addControls:#[self.textView]
align:VerticalAlignTop
withHeight:TEXT_VIEW_HEIGHT
verticalPadding: 0
horizontalPadding: 20
topPadding: topPadding];

iOS UITextField Outside of SuperView not Responding to Touches

I'm building an iOS 8 app that makes use of the new hidesBarsOnSwipe property on UINavgitationController to hide the nav bar while scrolling. At the same time that the nav bar hides, I'm also programmatically hiding the tab bar. On top of the tab bar, there is a text field which lets users comment on a post (much like Facebook). When the tab bar is hidden (by moving it downward and off the screen), the text field is moved down as well, so that it now sits at the bottom of the screen and so that there's no gap between the bottom of the screen and the text field.
So, things look great. But, turns out that the text field doesn't respond to touch events when it moves to the bottom of the screen. I did some digging and it appears that the reason is because the text field is outside of its superview (the view controller's view), and so touch events will not be sent to the text field.
So I think I've figured out why the issue is occurring, but I haven't yet figured out how to fix it. I've tried messing with hitTest:withEvent: and pointInside:withEvent: but didn't have any luck. Anyone have any solutions?
EDIT: Here is some code to make the question clearer (hopefully). When the nav controller's barHideOnSwipeGestureRecognizer is called, I am running the following code:
- (void)barHideSwipeGestureActivated:(UIPanGestureRecognizer*)gesture
{
[self animateTabBarUpOrDown:self.navigationController.navigationBar.frame.origin.y >= 0 completion:nil];
}
The method above is the following:
- (void)animateTabBarUpOrDown:(BOOL)up completion:(void (^)(void))completionBlock
{
if(!self.animatingTabBar && self.tabbarIsUp != up)
{
self.animatingTabBar = YES;
//to animate the tabbar up, reset the comments bottom constraint to 0 and set the tab bar frame to it's original place
//to animate the tabbar down, move its frame down by its height. set comments bottom constraint to the negative value of that height.
[UIView animateWithDuration:kTabBarAnimationDuration animations:^{
UITabBar *tabBar = self.tabBarController.tabBar;
if(up)
{
tabBar.frame = CGRectMake(tabBar.frame.origin.x, tabBar.frame.origin.y - tabBar.frame.size.height, tabBar.frame.size.width, tabBar.frame.size.height);
self.addCommentViewToBottomConstraint.constant = 0.0f;
}
else
{
tabBar.frame = CGRectMake(tabBar.frame.origin.x, tabBar.frame.origin.y + tabBar.frame.size.height, tabBar.frame.size.width, tabBar.frame.size.height);
self.addCommentViewToBottomConstraint.constant = -tabBar.frame.size.height;
}
} completion:^(BOOL finished) {
self.tabbarIsUp = up;
self.animatingTabBar = NO;
if(completionBlock)
{
completionBlock();
}
}];
}
}
Ok, finally found a solution for this one. I haphazardly messed around with changing the bounds of my view controller's view, but that was too hacky and ultimately didn't accomplish what I wanted it to.
What I ended up doing was changing my view controller's edgesForExtendedLayout property to be equal to UIRectEdgeAll which basically says that the view should take up the entire screen, and extend above top bars / below bottom bars.
I had to hack around a little bit with changing auto layout constraints on my text field so that it appeared in the right place at the right time, but overall, the solution was changing edgesForExtendedLayout to be UIRectEdgeAll - this makes the view take up the entire screen, so the text field is now still in the super view even when it animates downward, thus, allowing it to still receive touch events.

How to animate transition between two views without using two view controllers?

I am trying to simulate a deck of cards, where a swipe over the screen makes the next card enter the display from the right, covering the card that is currently visible. I know how to program this using two view controllers and custom segues, but I want to keep it simple. I therefore want to stay within one and the same view controller, copy the current view into a temporary view, draw the new view, and then animate the new view to drift in from the right and cover the old one. Here's my attempt:
- (IBAction)SwipeLeft:(id)sender
{ UIView *sv = self.view;
[self Layout]; // this is where the next card is drawn
UIView *dv = self.view;
dv.center = CGPointMake(sv.center.x + sv.frame.size.width, sv.center.y);
sv.center = CGPointMake(sv.center.x, sv.center.y);
[UIView animateWithDuration:0.3
animations:^
{ dv.center = CGPointMake(sv.center.x, sv.center.y);
sv.center = CGPointMake(sv.center.x - sv.frame.size.width, sv.center.y);
}];
NSLog(#"Swipe left");
}
When I run this code, I do see the new card enter the screen appropriately, however, the 'old' view dissappears immediately and turns into a black screen before the animation starts.
How can I correct this?
Clearly this code does not work, as both pointers point at the same memory location. I decided to solve my problem in a different way. Rather than trying to copy a View object, I create a screen shot, copy the UIImage into a View object, cover the screen under the copy of the old screen when I draw the new screen and than animate that View object. Probably a smarter solution. It works great.

Xcode 5 - Swipe switch view with constant background

I understand how to implement UISwipeGestureRecognizer, but I do not understand how to use multiple views properly in collaboration with it.
My ultimate goal is to provide a sleek interface where buttons or swiping may be used to change views. I am hoping for a constant background rather than a background image show it is transferring to the exact same image again. I am using storyboarding currently where each view has its own controller, and I would like (if possible) to keep it as close to this format for visual clarity of what is going on.
I have read that subviews may be my best hope for this sort of scenario (keeping them to the right until a swipe is called), but that would break up the current storyboarding structure I am using, and most certainly ruin any visual clarity.
Does anyone have any suggestions?
I did same programmatically, but you can achieve same using storyboard
1. You need base view which will keep same background
2. Add 3 Views (or 2 depending on ur requirement) Left, Middle, Right with transparent backgrounds as follows in viewDidLoad of base view controller
[self addChildViewController:myChildViewController];
[myChildViewController view].frame = CGMakeRect(x,y,width,height);//Left one will have x = -320, y=0 ,Middle x= 0, y= 0, Right x = 32O etc.
/* Add new view to source view */
[self.view addSubview:[myChildViewController view]];
/* Inform 'didMoveToParentViewController' to newly added view controller */
[myChildViewController didMoveToParentViewController:self];
3. In handle Swipe method change frames of view with animation which will make it look like swiping page
e.g
//swipe left, get right view in middle
[UIView animateWithDuration:0.2 animations:^{
CGRect frame = rightViewController.view.frame;
frame.origin.x = 0;
frame.origin.y = 0;
rightViewController.view.frame = frame;
CGRect middleViewFrame = middleViewController.view.frame;
middleViewFrame.origin.x = -320;
middleViewController.view.frame = middleViewFrame;
}];
You could use a navigation to do this. If you don't want the navigation bar, you can hide it. You can connect the left swipe gesture recognizer to a segue to the next controller, and connect the right swipe gesture recognizer to an unwind segue (or call popViewControllerAnimated: from its action method). If you want a constant background, then you need to add that to the navigation controller's view (as a background color), and have the child view controller's views be transparent (or partially so). This code in the navigation controller's root view controller will add the background:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"img.tiff"]];
}
I know exactly what you're talking about and I'm actually doing the same with my current project.
I accomplished this by using Interface Builder and a single view controller. Basically I placed a UIImageView in the view and set it to the background you wish, then I put a transparent UIScrollView on top of it. Inside that scroll view I put the two views I want to scroll horizontally through.
Don't forget to enable UIScrollView paging. In my case, I had two table views. Starting from the first one, if you swiped from right to left it would scroll to the next table view, and if you swiped left to right it would scroll back to the first.

Resources