UITextView setContentOffset:CGPointZero in layoutSubviews doesn't work in iOS8 - ios

I have a PopupView that extends UIView. In PopupView I have a UITextView.
When the PopupView show, my UITextView doesn't start at first line (it scroll a little bit to bottom)
So I use the code below to scroll the UITextView to top after PopupView appears
- (void)layoutSubviews{
[super layoutSubviews];
[self.contentTextView setContentOffset:CGPointZero animated:NO];
}
It works well in iOS9 (both device and simulator) but it doesn't work in iOS8
Any idea to fix it.
Any help would be great appreciated
UPDATE
I found that drawRect get called after layoutSubviews and if I setContentOffset:CGPointZero inside it, it will work
-(void)drawRect:(CGRect)rect{
[self.contentTextView setContentOffset:CGPointZero];
}
But I found the purpose of drawRect:
drawRect: - Implement this method if your view draws custom content.
If your view does not do any custom drawing, avoid overriding this
method.
Is it good to use drawRect without layoutSubviews in my case?

According to #longpham instruction, the drawRect() will use GPU so it's not good. Here is the solution that solve my problem
-(void)awakeFromNib{
[super awakeFromNib];
[self layoutIfNeeded]; // call layoutIfNeeded here to make layoutSubviews get called whenever layout change
}
- (void)layoutSubviews{
...
[self.contentTextView setContentOffset:CGPointZero];
}

it worked for me
- (void)drawRect:(CGRect)rect {
self.textView=[[UITextView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
[self addSubview:self.textView];
}

Related

iOS8 issue - UIView drawRect is not called

I have a very simple UIView, that is only drawing a triangle. It implements a UIView drawRect method that is drawing a figure. It is working fine on iOS7, but when I run the same code on iOS8 it is not even called. Any ideas what has changed on iOS8? My code in nutshell:
#interface MyView : UIView
#end
#implementation MyView
- (instancetype)init {
self = [super init];
if (self) {
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (void)drawRect:(CGRect)rect {
// draw things
}
#end
myView = [MyView new];
[self addSubview:myView];
Update
My view hierarchy: UIViewController View->Custom UILabel->MyView
It is an error message bubble, and my view is a beak pointing to something.
I have used a new UI debugging tool, but it is not visible there. Init method is called, drawRect is not. Something must have changed in iOS8, because iOS7 is calling drawRect.
Update 2
Of course I'm using constraints (and Masonry pod), and this is why I did not specify the frame.
[myView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(make.superview.mas_bottom);
make.centerX.equalTo(make.superview);
make.width.equalTo(#10.0f);
make.height.equalTo(#5.0f);
}];
I have also tried adding [myView setNeedsDisplay] in various places, but it didn't help.
Problem finally solved. I'm not sure what exactly caused the issue (Masonry [constraints framework] or iOS8 changes to UILabel), but the solution was to change the view hierarchy. I created another UIView that contains both UILabel and my UIView (drawn beak) instead of adding the UIView to UILabel as subview. Now drawRect method is called both on iOS7 and iOS8.
Previous hierarchy:
UIViewController View->Custom UILabel->MyView
New hierarchy:
UIViewController View->Container UIView->Custom UILabel & MyView
To sum up, if your drawRect method is not called on iOS8, and you are adding some UIView to UILabel as subview, try to use some container which will contain both UIView and UILabel.
make sure that myView's superview property clipToBounds = NO, and that myView rect is on screen
There is an important detail missing in your approach. You need to specify a frame that determines the origin and size of your view. This can be done with the initWithRect method of UIView, or you can set the frame after allocation/initialization. Since you have a custom init method already I would do the following:
myView = [MyView new];
myView.frame = CGRectMake(0.0,0.0,100.0,100.0);
[self addSubview:myView];
This will draw your custom view with an origin point of (0.0,0.0) and a width and height of 100.0.
Furthermore, adding a call to setNeedsDisplay should force the drawRect method to be called if you are still having trouble:
[myView setNeedsDisplay];

UITableViewController changes UITableView's frame when app enters back into foreground and becomes active

I have a UITableViewController, that is embedded in a UITabBarController and also managed by a UINavigationController.
The only place that I have been able to customize the UITableViewController's table view frame is in viewDidAppear:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:YES];
[self customizeTableViewAppearance];
}
Here is the customizeTableViewAppearance method:
- (void)customizeTableViewAppearance
{
self.tableView.contentInset = UIEdgeInsetsMake(20, 0, 0, 0);
if([UIScreen mainScreen].bounds.size.height == 568) {
[self.tableView setFrame:CGRectMake(0, 60, 320, 460)];
} else {
[self.tableView setFrame:CGRectMake(0, 60, 320, 370)];
}
}
This works perfectly when first using the app, but if you go to the device's home screen, and then resume using the app again, none of the usual view methods are called and the table view has been moved. So for whatever reason, even though view methods are not called, UITableViewController is changing the custom frame that I have set for it's UITableView.
Sure enough, if I move to a different tab, and then revisit the tab again, the view methods are called and the UITableView's frame is correct again.
How can I make it so that if the user leaves the app, and then resumes the app again later, that my frame will stay set and not be reset by the UITableViewController?
It's not really clear why you are manipulating the frame of your UITableViewController's tableView, but in most cases, you shouldn't.
From what you pasted, it seems like are trying to prevent the tableView or its content from appearing underneath your navbar, and your tabbar.
Instead of changing the tableView's frame, you should try one of the following things:
Try setting self.edgesForExtendedLayout = UIRectEdgeNone when initialising your UITableViewController
or:
Make sure self.automaticallyAdjustsScrollViewInsets = YES when initialising your UITableViewController
or, if for some reason you need to manage your tableView's contentInset manually:
Make sure self.automaticallyAdjustsScrollViewInsets = NO when initialising your UITableViewController. Now implement viewDidLayoutSubviews as follows
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.tableView.contentInset = UIEdgeInsetsMake(self.topLayoutGuide.length, 0, self.bottomLayoutGuide.length, 0);
}
Edit:
I just saw you're using a Storyboard. You can either set the edgesForExtendedLayout or automaticallyAdjustsScrollViewInsets from within your storyboard, or set them by implementing the -(void)awakeFromNib; method.
Without knowing why you are doing what you're doing, an easy solution to handle that case would be the following - Add this in your tableView's init method:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(customizeTableViewAppearance) name:UIApplicationDidBecomeActiveNotification object:nil];
That way, any time your application becomes active again your tableView will call that method.
The way to fix this was to override the viewDidLayoutSubviews method and place my customizeTableViewAppearance method inside of it:
- (void)viewDidLayoutSubviews
{
[self customizeTableViewAppearance];
}
The UITableView always has the correct frame now.

UIScrollView child jumping after UINavigationController push and pop

So I have a UIScrollView on my iPad app with a single child view (which itself is parent to all the controls). The scrolling all works fine on it. Rotating works fine (the whole view fits in portrait, scrolls on landscape). Once pushing a new screen on the UINavigationController, and then coming back breaks it.
It looks as if the frame of the scrollview's child has moved up, relative to the scroll position, but the scrollview has remained at the bottom (the entire child view has shifted upwards).
I've tried fighting the Constraints in storyboard, literally for hours, and cannot work out what could be causing this.
I had the same problem with scroll view and auto layouts (iOS 6 - doesn't work, iOS 7 - works fine), of course this is not perfect solution, but seems like it works. Hope it will help you:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self performSelector:#selector(content) withObject:nil afterDelay:0.0];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
offset = self.scrollView.contentOffset;
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
self.scrollView.contentOffset = CGPointZero;
}
- (void)content
{
[self.scrollView setContentOffset:offset animated:NO];
}
Get the frame of the subview before it disappears then manually reset the frame of the subview every time the view appears in -(void)viewWillAppear:(BOOL)animated.
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
globalFrameVariable = subview.frame;
[subview removeFromSuperview];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[subview setFrame:globalFrameVariable];
[scrollView addSubview:subview];
}
Here is a simple solution i found. (Assuming the parent view is meant to span the entire contentSize) Use this subclass of UIScrollView:
#interface BugFixScrollView : UIScrollView
#end
#implementation BugFixScrollView
-(void)layoutSubviews
{
[super layoutSubviews];
UIView *view=[self.subviews firstObject];
if(view)
{
CGRect rect=view.frame;
rect.origin=CGPointMake(0, 0);
view.frame=rect;
}
}
#end
It simply resets the origin every time auto-layout messes it up. this class can be used in InterfaceBuilder simply by changing the class name after placing the UIScrollView.

Table view cell isn't transparent until I force the cell to reload?

I have a custom UITableView with a UIImageView as its background. I want to display some transparent cells in the table so that I can see the background image through the cell's background image.
So far, I'm setting the cell's backgroundView to a UIImageView with a custom alpha and setting the background image view's backgroundColor to [UIColor clearColor]. I'm also setting the cell's backgroundColor to [UIColor clearColor].
When the cell is initially drawn, I still see a white background. However, if I drag the cell off the screen, it's transparent when it comes back on. Anyone have any idea what's up with that?
Edit: Here's some code, though I don't think it really tells you anything more than what I said above. Some sensitive/irrelevant stuff is edited out.
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if((self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) {
[self setBackgroundColor:[UIColor clearColor]];
...
_backgroundImageView = [[UIImageView alloc] initWithFrame:[[self contentView] bounds]];
[_backgroundImageView setAlpha:0.5];
[_backgroundImageView setOpaque:NO];
[_backgroundImageView setBackgroundColor:[UIColor clearColor]];
[self setBackgroundView:_backgroundImageView];
}
return self;
}
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
[_backgroundImageView setFrame:rect];
}
// in a different file
- tableView:cellForRowAtIndexPath: {
...
[cell setBackgroundColor:[UIColor clearColor]];
[cell setOpaque:NO];
return cell;
}
Have you called setNeedsDisplay?
remove this
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
[_backgroundImageView setFrame:rect];
}
add this
-(void)layoutSubviews{
[_backgroudImageVIew setFrame:self.contentView.bounds];
}
why are you doing this:
[_backgroundImageView setAlpha:0.5];
Figured out the problem. This was actually due to a weird interaction of UITableView with the cells, not the cells themselves. As I mentioned, I'm using a custom table view with a UIImageView background, but I made the mistake of delaying setting the table view's backgroundView property to the image view until drawRect: instead of doing it in init or awakeFromNib or anywhere else that would make sense.
Setting the background view of the table view actually changes what the cells do for some reason. Previously, when I was changing the background view in drawRect:, the cells would be drawn as if the table view had a white background, then update when they got redrawn. Now that I've moved things into an initializer method everything's fine.
Sorry, I didn't provide code for the table view because it didn't even occur to me that it could be causing the problem (nor did it occur to me that I was an idiot and put a line of code in the wrong method... don't code at 4 AM :/)

Disable UIScrollView scrolling when UITextField becomes first responder

When a UITextField embedded in a UIScrollView becomes the first responder (for example, by the user typing in some character), the UIScrollView scrolls to that Field automatically. Is there any way to disable that?
Duplicate rdar://16538222
Building on Moshe's answer... Subclass UIScrollView and override the following method:
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
Leave it empty. Job done!
In Swift:
class CustomScrollView: UIScrollView {
override func scrollRectToVisible(_ rect: CGRect, animated: Bool) { }
}
I've been struggling with the same problem, and at last I've found a solution.
I've investigated how the auto-scroll is done by tracking the call-trace, and found that an internal [UIFieldEditor scrollSelectionToVisible] is called when a letter is typed into the UITextField. This method seems to act on the UIScrollView of the nearest ancestor of the UITextField.
So, on textFieldDidBeginEditing, by wrapping the UITextField with a new UIScrollView with the same size of it (that is, inserting the view in between the UITextField and it's superview), this will block the auto-scroll. Finally remove this wrapper on textFieldDidEndEditing.
The code goes like:
- (void)textFieldDidBeginEditing:(UITextField*)textField {
UIScrollView *wrap = [[[UIScrollView alloc] initWithFrame:textField.frame] autorelease];
[textField.superview addSubview:wrap];
[textField setFrame:CGRectMake(0, 0, textField.frame.size.width, textField.frame.size.height)];
[wrap addSubview: textField];
}
- (void)textFieldDidEndEditing:(UITextField*)textField {
UIScrollView *wrap = (UIScrollView *)textField.superview;
[textField setFrame:CGRectMake(wrap.frame.origin.x, wrap.frame.origin.y, wrap.frame.size.width, textField.frame.size.height)];
[wrap.superview addSubview:textField];
[wrap removeFromSuperview];
}
hope this helps!
I had the same issue with disabling auto-scrolling of a UITextView being a cell of UITableView. I was able to resolve it using the following approach:
#interface MyTableViewController : UITableViewController<UITextViewDelegate>
#implementation MyTableViewController {
BOOL preventScrolling;
// ...
}
// ... set self as the delegate of the text view
- (void)textViewDidBeginEditing:(UITextView *)textView {
preventScrolling = YES;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (preventScrolling) {
[self.tableView setContentOffset:CGPointMake(0, -self.tableView.contentInset.top) animated:NO];
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
preventScrolling = NO;
}
Defining scrollViewWillBeginDragging is used for restoring the default scrolling behaviour, when the user himself initiates scrolling.
As Taketo mentioned, when a UITextField is made first responder, its first parent view that is of type UIScrollView (if one exists) is scrolled to make that UITextField visible. The easiest hack is to simply wrap each UITextField in a UIScrollView (or ideally, wrap all of them in a single dummy UIScrollView). This is very similar to Taketo's solution, but it should give you slightly better performance, and it will keep your code (or your interface in Interface Builder) much cleaner in my opinion.
Building on Luke's answer, to handle the issue that his solution completely disables auto-scroll, you can disable it selectively as follows:
// TextFieldScrollView
#import <UIKit/UIKit.h>
#interface TextFieldScrollView : UIScrollView
#property (assign, nonatomic) IBInspectable BOOL preventAutoScroll;
#end
#implementation TextFieldScrollView
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated {
if (self.preventAutoScroll == NO) {
[super scrollRectToVisible:rect animated:animated];
}
}
#end
This way, you can completely set it up in Interface Builder to disable the auto-scroll, but have full control at any time to re-enable it (though why you'd want to is beyond me).
It looks like UIScrollview which contains UITextfield, auto adjusts its content offset; when textfield is going to become first responder.
This can be solved by adding textfield in scrollview of same size first, and then adding in to main scroll view. instead of directly adding in to main scrollview
// Swift
let rect = CGRect(x: 0, y: 0, width: 200, height: 50)
let txtfld = UITextField()
txtfld.frame = CGRect(x: 0, y: 0, width: rect.width, height: rect.height)
let txtFieldContainerScrollView = UIScrollView()
txtFieldContainerScrollView.frame = rect
txtFieldContainerScrollView.addSubview(txtfld)
// Now add this txtFieldContainerScrollView in desired UITableViewCell, UISCrollView.. etc
self.mainScrollView.addSubview(txtFieldContainerScrollView)
// Am33T
This is the way I do it:
It is very simple, you get to return your own contentOffset for any scrollRectToVisible.
This way you are not harming the normal behaviour and flow of things - just providing the same functionality in the same channel, with your own improvements.
#import <UIKit/UIKit.h>
#protocol ExtendedScrollViewDelegate <NSObject>
- (CGPoint)scrollView:(UIScrollView*)scrollView offsetForScrollingToVisibleRect:(CGRect)rect;
#end
#interface ExtendedScrollView : UIScrollView
#property (nonatomic, unsafe_unretained) id<ExtendedScrollViewDelegate> scrollToVisibleDelegate;
#end
#import "ExtendedScrollView.h"
#implementation ExtendedScrollView
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
{
if (_scrollToVisibleDelegate && [_scrollToVisibleDelegate respondsToSelector:#selector(scrollView:offsetForScrollingToVisibleRect:)])
{
[self setContentOffset:[_scrollToVisibleDelegate scrollView:self offsetForScrollingToVisibleRect:rect] animated:animated];
}
else
{
[super scrollRectToVisible:rect animated:animated];
}
}
#end
I've tried #TaketoSano's answer, but seems not works.. My case is that I don't have a scrollview, just a view with several text fields.
And finally, I got a workaround. There're two default notification names for keyboard that I need:
UIKeyboardDidShowNotification when the keyboard did show;
UIKeyboardWillHideNotification when the keyboard will hide.
Here's the sample code I used:
- (void)viewDidLoad {
[super viewDidLoad];
...
NSNotificationCenter * notificationCetner = [NSNotificationCenter defaultCenter];
[notificationCetner addObserver:self
selector:#selector(_keyboardWasShown:)
name:UIKeyboardDidShowNotification
object:nil];
[notificationCetner addObserver:self
selector:#selector(_keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)_keyboardWasShown:(NSNotification *)note {
[self.view setFrame:(CGRect){{272.f, 55.f}, {480.f, 315.f}}];
}
- (void)_keyboardWillHide:(NSNotification *)note {
[self.view setFrame:(CGRect){{272.f, 226.5f}, {480.f, 315.f}}];
}
Here, the (CGRect){{272.f, 226.5f}, {480.f, 315.f}} is view's default frame when keyboard is hidden. And (CGRect){{272.f, 55.f}, {480.f, 315.f}} is view's frame when keyboard did show.
And b.t.w., the view's frame changing will be applied animation automatically, this's really perfect!
I have a collection view with a text field at the very top, mimicking the UITableView.tableHeaderView. This text field is located in the negative content offset space so that it doesn't interfere with the rest of the collection view. I basically am detecting whether or not the user is performing the scrolling in the scroll view and whether or not the text field is first responder and if the scroll view is being scrolled beyond the top of the scroll view's content inset. This exact code won't necessarily help anyone but they could manipulate it to fit their case.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// This is solving the issue where making the text field first responder
// automatically scrolls the scrollview down by the height of the search bar.
if (!scrollView.isDragging && !scrollView.isDecelerating &&
self.searchField.isFirstResponder &&
(scrollView.contentOffset.y < -scrollView.contentInset.top)) {
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, -scrollView.contentInset.top) animated:NO];
}
}
I don't know of any property of UIScrollView that would allow that. It would be poor user experience to be able to disable that, IMHO.
That said, it may be possible to subclass UIScrollView and override some of its methods to check that the UITextfield is not a first responder before scrolling.
An easier way to stop the scrollview scrolling when you select a textField is in your viewController::viewWillAppear() DO NOT call [super viewWillAppear];
You can then control the scroll as you wish.

Resources