iOS uilabel stay on screen when scrolling - ios

In my app I was giving some information like "connection error" or "image saved" when users
caused some events. Instead of using alert view, I'd like to show labels with those text
on screen for
about 2-3 secs and disappear automatically.
Most like what Android apps would do like this.
(sorry for a link rather than post image due to low rep.)
As I did [superView addSubview:label] my label was attached on the back view and moved by scrolling.(superView = tableView)
How could I keep it at a fixed position of screen when user's scrolling it's superView?
prefer simple solution without using 3rd party...
tho any advance would be appreciated.

just add a label to the superview and make it hidden
label.hidden = YES; // write this in viewDidLoad()
And in event action unhide the label
label.hidden = NO;
and then set a timer so that after 2 or 3 second the label will disapear.
//inside event action
[NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(hideLabel) userInfo:nil repeats:NO];
and in the selector method write code for hiding the label
- (void) hideLabel
{
_lblClick.hidden = YES;
}
I hope this is what you want......

After you add the view to your superView , You can use the [superView bringSubviewToFront:<#(UIView *)#>] method. This will block some of your scrollView scrolling space, but will keep your label in the frontmost part of your view.
You can use UIView's animation to add to show and hide the label

Don't add the label to scrolling superView. Instead add it to parent of superView or the viewControllers primaryView(by default self.view) and use hidden property. Use autolayout to position the label for various layouts.

Related

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

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.

Scroll bar visibility in iOS

I got stuck where I need to display my UITextView's scroll bar permanently i.e. scroll bar should not disappear. I tried [textview flashScrollIndicators]; for this but it doesn't make any difference. Is there any way to do this?
[textView flashScrollIndicators];
UITextView inherit UIScrollView. As UIScrollView has a method flashScrollIndicators to show the scroll bar that you can call to prompts the user that the view is scrollable. Note, It only flashes once for few seconds where user comes to a page that contains UIScrollView or its subclass. As you want to show the scroll bar permanently show you can use a timer to call this method.
[NSTimer scheduledTimerWithTimeInterval:2.0f target:self selector:#selector(showAgain) userInfo:nil repeats:YES];
-(void)showAgain
{
[myTextView flashScrollIndicators];
}
NB: UITextView only start showing the scroller when it's content size get overflow then it's height. And there after [textView flashScrollIndicators] this method will work.
No there is no way to have them always display. Also they're scroll indicators and not scroll bars.
You could setup a timer to flashScrollIndicators at a given time suited to your app such as when the view appears or when some text input is detected using the UITextView's delegates.
I haven't tried using this custom component but since the UITextView is a subclass of UIScrollView, you could explore the possibility of tinkering with it and see if it works for you.
Try to set flashIndicators in viewDidAppear or viewDidLoad
-(void)viewDidAppear:(BOOL)animated {
[self.textView flashScrollIndicators];
}

Animate UIView along keyboard appear animation

I am using UIKeyboardWillShowNotification and UIKeyboardWillHideNotification to animate a view along the keyboard appear animation using UIKeyboardAnimationDurationUserInfoKey, UIKeyboardAnimationCurveUserInfoKey and UIKeyboardFrameEndUserInfoKey.
Everything works fine, as long as the elements start position is in the bottom of the screen. My element (input box in the screenshot) starts above the UITabBarController, so if my animation starts there is gap between keyboard and UITextField, which shrinks along the animation, till it reaches its end.
What I`m searching for is something like: "Animate with same animation curve, but start the moving, if keyboard reaches my maxY position".
If I would add a delay for starting the animation it would not be correct with the easing and this may break in future iOS releases.
It would be great if you share your ideas with me. :-)
There are typically two approaches you might use to keep a view above the keyboard as it animates into place. As you know, the first is to listen for the UIKeyboardWillShowNotification and use the accompanying duration/curve/frame values in the userData to help you position and animate your view above the keyboard.
A second approach is to supply an inputAccessoryView for the view (UITextField, here) that is invoking the keyboard. (I realize this won't provide the effect you're asking for, which is to "push" the toolbar/textfield up once the keyboard runs into it. But more on this later.) iOS will parent your inputAccessoryView to the view that also parents the keyboard and animate them in together. In my experience this provides the best-looking animation. I don't think I've ever had perfect animation using the UIKeyboardWillShowNotification approach, especially now in iOS7 where there's a little bounce at the end of the keyboard animation. There's probably a way with UIKit Dynamics to apply this bounce to your view too, but making it perfectly in sync with the keyboard would be hard.
Here's what I've done in the past for a scenario similar to yours: there is bottom-positioned UIToolbar having a UITextField in a customView bar button item for input. In your case this is positioned above a UITabBar. The ITextField has a custom inputAccessoryView set, which is another UIToolbar with another UITextField.
When the user taps into the text field and it becomes first responder, the keyboard animates into place with the 2nd toolbar/textfield along with it (and this transition looks very nice!). When we notice this happening we transition the firstResponder from the first text field to the second such that it has the blinking caret once the keyboard is in place.
The trick is what to do when you determine its time to end editing. First, you have to resignFirstResponder on the second text field, but if you're not careful then the system will pass first responder status back to the original text field! So you have to prevent that, because otherwise you'll be in an infinite loop of passing forward the first responder, and the keyboard will never dismiss. Second, you need to mirror any text input to the second text field back to the first text field.
Here's the code for this approach:
#implementation TSViewController
{
IBOutlet UIToolbar* _toolbar; // parented in your view somewhere
IBOutlet UITextField* _textField; // the customView of a UIBarButtonItem in the toolbar
IBOutlet UIToolbar* _inputAccessoryToolbar; // not parented. just owned by the view controller.
IBOutlet UITextField* _inputAccessoryTextField; // the customView of a UIBarButtonItem in the inputAccessoryToolbar
}
- (void) viewDidLoad
{
[super viewDidLoad];
_textField.delegate = self;
_inputAccessoryTextField.delegate = self;
_textField.inputAccessoryView = _inputAccessoryToolbar;
}
- (void) textFieldDidBeginEditing: (UITextField *) textField
{
if ( textField == _textField )
{
// can't change responder directly during textFieldDidBeginEditing. postpone:
dispatch_async(dispatch_get_main_queue(), ^{
_inputAccessoryTextField.text = textField.text;
[_inputAccessoryTextField becomeFirstResponder];
});
}
}
- (BOOL) textFieldShouldBeginEditing: (UITextField *) textField
{
if ( textField == _textField )
{
// only become first responder if the inputAccessoryTextField isn't the first responder.
return ![_inputAccessoryTextField isFirstResponder];
}
return YES;
}
- (void) textFieldDidEndEditing: (UITextField *) textField
{
if ( textField == _inputAccessoryTextField )
{
_textField.text = textField.text;
}
}
// invoke this when you want to dismiss the keyboard!
- (IBAction) done: (id) sender
{
[_inputAccessoryTextField resignFirstResponder];
}
#end
There's one final possibility I can think of. The approach above has the drawback of two separate toolbars/textfields. What you ideally want is just one set of these, and you want it to appear that the keyboard "pushes" them up (or pulls them down). In reality the animation is fast enough that I don't think most people would notice there are two sets for the above approach, but maybe you don't like that..
This final approach listens for the keyboard to show/hide, and uses a CADisplayLink to synchronize animating the toolbar/textfield as it detects changes in the keyboard position in real time. In my tests it looks pretty good. The main drawback I see is that the positioning of the toolbar lags a tiny bit. I'm using auto-layout and changing over to traditional frame-positioning might be faster. Another drawback is there is a dependency on the keyboard view hierarchy not changing dramatically. This is probably the biggest risk.
There's one other trick with this. The toolbar is positioned in my storyboard using constraints. There are two constraints for the distance from the bottom of the view. One is tied to the IBOutlet "_toolbarBottomDistanceConstraint", and this is what the code uses to move the toolbar. This constraint is a "vertical space" constraint with a "Equal" relation. I set the priority to 500. There is a second parallel "vertical space" constraint with a "Greater than or equal" relation. The constant on this is the minimum distance to the bottom of the view (above your tab bar, for example), and the priority is 1000. With these two constraints in place I can set the toolbars distance-from-bottom to any value I like, but it will never drop below my minimum value. This is key to making it appear that the keyboard is pushing/pulling the toolbar, but having it "drop off" the animation at a certain point.
Finally, perhaps you could make a hybrid of this approach with what you've already got: use a CADisplayLink callback to detect when the keyboard has "run into" your toolbar, then instead of manually positioning the toolbar for the remainder of the animation, use a real UIView animation to animate your toolbar into place. You could set the duration to be the keyboard-display-animation-duration minus the time already transpired.
#implementation TSViewController
{
IBOutlet UITextField* _textField;
IBOutlet UIToolbar* _toolbar;
IBOutlet NSLayoutConstraint* _toolbarBottomDistanceConstraint;
CADisplayLink* _displayLink;
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
}
- (void) viewDidLoad
{
[super viewDidLoad];
[self.view addGestureRecognizer: [[UITapGestureRecognizer alloc] initWithTarget: self action: #selector( dismiss:) ]];
_textField.inputAccessoryView = [[UIView alloc] init];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(keyboardWillShowHide:)
name: UIKeyboardWillShowNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(keyboardWillShowHide:)
name: UIKeyboardWillHideNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(keyboardDidShowHide:)
name: UIKeyboardDidShowNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(keyboardDidShowHide:)
name: UIKeyboardDidHideNotification
object: nil];
}
- (void) keyboardWillShowHide: (NSNotification*) n
{
_displayLink = [CADisplayLink displayLinkWithTarget: self selector: #selector( tick: )];
[_displayLink addToRunLoop: [NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes];
}
- (void) keyboardDidShowHide: (NSNotification*) n
{
[_displayLink removeFromRunLoop: [NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes];
}
- (void) tick: (CADisplayLink*) dl
{
CGRect r = [_textField.inputAccessoryView.superview.layer.presentationLayer frame];
r = [self.view convertRect: r fromView: _textField.inputAccessoryView.superview.superview];
CGFloat fromBottom = self.view.bounds.size.height - r.origin.y;
_toolbarBottomDistanceConstraint.constant = fromBottom;
}
- (IBAction) dismiss: (id) sender
{
[self.view endEditing: YES];
}
#end
Here's the view hierarchy and constraints:
I don't have a detailed solution as Tom provided, but I do have an idea you could play around with. I've been doing a lot of interesting things with auto layout and constraints, and you can do some amazing things. Note that you cannot constrain items in a scroll view with things that are in one.
So you have your primary view, I assume its a table view or some other view inside a scrollView, so you have to deal with that. The way I suggest is to take a snapshot of the view, save the current view in an ivar (your table), and replace it with a "very tall container view" that is anchored on the bottom, put the UIImageView containing the snapshot into this view, with a constraint between it and the container view of constant=0. To the user nothing changed.
In the "inputAccessoryView", when the view is added to the superView (and when there is a window property), you can remove the constraint between the image and the container view, and add a new one that constrains the bottom of the text field to the top of your inputAccessoryView, where the distance has to be greater than some value. You have to play around to get the value, as it will be the offset of that textField in your scrollView adjusted for any contentValue. Then, you will most likely have to add that constraint to the window (keeping an ivar to it so you can remove it later).
In the past I played around with the keyboard, and you can see that it gets added to the window, with its frame offset so its just below the bottom of the screen (was in iOS5) - it was not in a scrollView.
When the keyboard finishes scrolling, you can see where the image view has scrolled, determine the offset, then do the switch back from the image view to your real scrollview.
Note that I did do this snapshot, animate, finally replace views in the past quite successfully. You will spend some time on this, and maybe it will work and maybe not - but if you throw together a simple demo project you can verify quickly if you can get an imageView itself in a container view to move using constraints on the keyboard input accessory view. Once that works you can do it "for real".
EDIT: As Tom Swift has pointed out, the keyboard is located in another window at a higher "Z" level, and thus there is no way to directly connect a constraint between the real keyboard and a user view.
However - the in the keyboard notifications, we can get the size of it, the animation duration, even the animation curve. So, when you get the first keyboard notification, create a new transparent view and place it so its top is at the bottom of your special "imageView" (snapshot) view. Use a UIView animation of the length and curve of the keyboard, and your transparent view will animate exactly as the keyboard is animation - but in your window. Place the constraints on the transparent view, and you should be able to achieve the exact behavior you want (in iOS6). Really, supporting iOS 5 at this point - for the 10 people who haven't upgraded yet?!?!?.
If you have to support iOS5, and you want this "bump" behavior, then compute when the animation will reach a size where it "hits" your textField, and when the keyboard starts moving use the UIView animation with delay, so that it doesn't start moving right away, but when it does move, it tracks the keyboard.

center title and button in a custom sized UIAlertView iOS

I am working with alertViews to load to them different objects, such as textFields and others.
With textFields there is no problem. I have successfully added a UIPickerView as a subview of my alertView and I had resized the alertView.frame to hold the pickerView properly, but then the title and the button in the alertView are not centered.
I tried with many of the options [alertView …function…] but none seems to work with this issue. This looks bad in a custom sized alertView. Any suggestions?
Thanks folks!
to solve the Title issue, I got inspiration from this post: Custom AlertView With Background
First of all, when the App presents the alertView, I called a NSLog to get:
the pickerView's (my subview) width
the _titleLabel's width
the _titleLabel's starting position (MinX and MinY)
then I resized the _titleLabel frame and that was all!
textAlignment is Center by default so all I had to do was resize the label frame. other possible method is to use
[theTitle CGAffineTransformMakeTranslation(newXValue, 0];
Y value is 0 because I didn't want it to move vertically. But the described method is cleaner to me.
so what I added to the method that creates&presents my alertView was this:
NOTE: this code HAS to go BEFORE the addition of any additional view to the alertView!
//... alertView creation and display
//create a title object to edit titleLable properties
UILabel *theTitle = [pickers valueForKey:#"_titleLabel"];
// set frame for titleLabel to begin where it currently begins in both X and Y, set its width to the width of th e picker +/- the difference in X in case they don't have the same MinX value, and set the height it already has
theTitle.frame = CGRectMake(CGRectGetMinX(theTitle.frame), CGRectGetMinY(theTitle.frame), CGRectGetWidth(pkPais.frame)+6, CGRectGetHeight(theTitle.frame));
//...add subviews and set its frames (in my case, a pickerView)
as for the button, i found in the UIAlertView.h file that buttons cannot be customized...so if someone has any tip on this issue, any help will be appreciated.
hope this helps someone else!
UPDATE: As I mentioned, Apple said buttons in alertViews cannot be customized, so there are two alternatives: create a custom button and add it as subview or have no button at all. In my case I did so, and called [myAlertView dismissWithClickedButtonIndex:-1 animated:YES] in the didSelectRow...inComponent part of the pickerView code.
This worked for me just fine, but I am open to new ideas!!
See ya!

Resources