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

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

Related

How to prevent iOS from resizing your UIViewController's view after return from background

I have a UIViewController that displays a form with several text fields. In order to prevent the text fields from getting blocked by the keyboard, I resize the controller's view when the keyboard appears and disappears.
However, when the keyboard is up, the user presses the home button, and then returns to the app, the controller's view will be resized again to the size it was before the keyboard was up and the keyboard will still be showing.
What's causing my controller's view to be resized on return from background, and how can I prevent it?
Maybe you need to nest a UIView,for example
_backgroundView = [UIView new];
_backgroundView.backgroundColor = [UIColor whiteColor];
_backgroundView.frame = CGRectZero;
[self.view addSubview:_backgroundView];
[_backgroundView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.top.mas_equalTo(self.view);
make.height.mas_equalTo(self.view.mas_height);
}];
then you need add your custom UIView to this backgroundView.
as you said,UIViewController's view will be resized after return from background. so you can nest a UIView of the same size as self.view,and add your custom UIView to this UIView.
In order to prevent the text fields from getting blocked by the keyboard, you can resize this backgroundView when the keyboard appears and disappears. and this time when you click the home button to enter the background or return from background,self.view won't be resized and backgroundView won't be resized too.
Although it is a bit of a hassle, this will solve your problem and will not affect the user experience anymore. And if you have a better solution, please let me know
It sounds like you are setting the frame and not using autolayout. When the view reappears viewDidLayoutSubviews gets called and your frame gets recalculated obliterating your previous change. You can either:
1) Move your frame to viewDidLayoutSubviews and change its size only if the keyboard is showing.
2) Use autolayout and simply pull up your bottom constraint .constant by an amount equal to your keyboard height.
In both cases you should call layoutIfNeeded to trigger autolayout/viewDidLayoutSubviews when the keyboard appears/disappears. This behavior is a good example of why you should not manipulate your frames outside of viewDidLayoutSubviews except for transitory animations.

UIButton created outside view not working

I have an application I'm making in storyboards. I want to have a tutorial-type of view. I decided to go with a freeform view controller and am filling it with 600x600 views that count as the pages. The problem I'm having is that when I have a UI button animate to the next page, the buttons that were created outside of the visible view don't seem to work. I even moved a view over so the button was half-visible and only half the button works when I move the view over.
Here's my next page code:
- (void)nextPage {
if (scrolling) return;
scrolling = YES;
[UIView animateWithDuration:0.3 animations:^{
CGRect frame = self.tutorialView.frame;
frame.origin.x -= 50; //frame.size.width;
[self.tutorialView setFrame:frame];
}];
scrolling = NO;
}
I currently have it moving only 50px instead of the whole page for testing purposes.
For testing purposes, I've started it half-way through and only half the button works. I've started it with the second view on top of the other one halfway visible and the same thing happens (only half the button works). Otherwise, when I tap the next button on the first view the buttons on the second view don't work (buttons created outside the initial view).
Views don't receive touch events where they're outside the bounds of their superview. You'll need to increase the button's superview's size.
You can visualize this behavior by setting clipsToBounds = YES - then you'll only see the touchable area of your button.
(You can override this behavior, but you probably shouldn't.)

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.

iOS 7 UIScrollView doesn't scroll when presented as modal view controller, works fine otherwise

I have a storyboard in which I have a view controller, (InfoViewController) in which I have an UIScrollView with some labels, uitextviews, etc. this is all created in IB, no code has been written at all. The only thing that is left for me to do is to set the content size, which I do as following:
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
CGRect screenRect = [[UIScreen mainScreen] bounds];
[self.scrollView setContentSize:CGSizeMake(screenRect.size.width, 600)];
[self.scrollView setBackgroundColor:[UIColor greenColor]];
}
Whenever I make this view the entry point of my app, it works perfectly. I can see my view, the content size is set, the background color is being set to green.
Now it comes, I created another view controller, and this view controller is now my entry point of the app. I added a button in there, and on this button I did a "modal segue" to the earlier mentioned Info View Controller.
When I now run my app, I press this button, my Info View Controller shows up. The green background color is being set, but it's impossible to scroll. So the code is being executed (otherwise the background color couldn't been green, in the storyboard it's just plain white) but somehow whenever I use this "modal segue", the scroll functionality gets lost.
How can I fix this?
Try to insert a UIView into the scroll view...
Set the UIView with top, bottom, leading and trailing space to super view to 0.
Then insert everything into the UIView rather than into the ScrollView
Then modify the constraint height of the inner UIView instead of the contentsize of the scroll view, it works with iOS7

Animate UITextview change out, Keep keyboard in place

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.

Resources