I created a simple UIView with a UILabel and a UITextField inside of this UIView, this appear fine in the screen but when I touch over the UITextField, the keyboard doesn't appear.
I'm using autolayout programmatic and I'm think the problem is something about UIVIew frame.
I made a simple code to simulate this problem, the most relevant part of code is:
FormInput interface.
//FormInput.h
#import <UIKit/UIKit.h>
#interface FormInput : UIView
#property (nonatomic, strong) NSString *title;
#end
FormInput implementation
// FormInput.m
#import "FormInput.h"
#interface FormInput () <UITextFieldDelegate>
#property (nonatomic, strong) UILabel *label;
#property (nonatomic, strong) UITextField *field;
- (void)setuSubviews;
#end
#implementation FormInput
- (id)init
{
self = [super init];
if(!self) return nil;
[self setuSubviews];
return self;
}
- (void)setuSubviews
{
NSDictionary *views = #{
#"label":self.label,
#"field":self.field
};
[self addSubview:self.label];
[self addSubview:self.field];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[label(<=120)]-2-[field]-0-|" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[label]" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[field]" options:0 metrics:nil views:views]];
}
#pragma mark - Lazy properties
- (UILabel *)label
{
if(!_label)
{
_label = [UILabel new];
_label.textColor = [UIColor blackColor];
_label.translatesAutoresizingMaskIntoConstraints = NO;
}
return _label;
}
- (UITextField *)field
{
if(!_field)
{
_field = [UITextField new];
_field.translatesAutoresizingMaskIntoConstraints = NO;
_field.font = [UIFont systemFontOfSize:16];
_field.placeholder = #"enter text here";
_field.keyboardType = UIKeyboardTypeDefault;
_field.keyboardAppearance = UIKeyboardAppearanceDark;
_field.returnKeyType = UIReturnKeyDone;
_field.clearButtonMode = UITextFieldViewModeWhileEditing;
_field.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
}
return _field;
}
#pragma mark - Properties setters
- (void)setTitle:(NSString *)title
{
_title = title;
self.label.text = title;
}
#end
View controller
#import "ViewController.h"
#import "FormInput.h"
#interface ViewController ()
#property (nonatomic, strong) FormInput *username;
#property (nonatomic, strong) FormInput *email;
- (void)setupLayout;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = [UIColor whiteColor];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// [self setupLayout];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self setupLayout];
}
#pragma mark - Lazy properties
- (FormInput *)username
{
if(!_username)
{
_username = [FormInput new];
_username.translatesAutoresizingMaskIntoConstraints = NO;
_username.title = #"Username:";
}
return _username;
}
- (FormInput *)email
{
if(!_email)
{
_email = [FormInput new];
_email.translatesAutoresizingMaskIntoConstraints = NO;
_email.title = #"Email:";
}
return _email;
}
#pragma mark - Layout setup
- (void)setupLayout
{
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btn.translatesAutoresizingMaskIntoConstraints = NO;
[btn setTitle:#"Button" forState:UIControlStateNormal];
UITextField *field = [UITextField new];
field.translatesAutoresizingMaskIntoConstraints = NO;
field.placeholder = #"Placeholder";
[self.view addSubview:self.username];
[self.view addSubview:self.email];
[self.view addSubview:btn];
[self.view addSubview:field];
NSDictionary *views = #{
#"username" : self.username,
#"email" : self.email,
#"btn" : btn,
#"field" : field
};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[username]-|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[email]-|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[btn]-|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[field]-|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-30-[username]-30-[email]-30-[btn]-30-[field]" options:0 metrics:nil views:views]];
}
#end
Make sure parent containers of the text field in question have "User Interaction Enabled" checked. Maybe you checked it for the text field but not your viewcont?
My reputation is "1" and I can't poste an image, but this check button should be in the interaction part...
Ok I found the reason.
The height of each view in the constraint below still intrinsic:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-30-[username]-30-[email]-30-[btn]-30-[field]" options:0 metrics:nil views:views]]
Thus, just add a vertical height for each view in the constraint visual format below.
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-30-[username(20)]-30-[email(20)]-30-[btn]-30-[field]" options:0 metrics:nil views:views]]
Not trivial but this makes the views inside added view to responds touch actions.
Thanks!
Related
I have created a custom view and added a UITextField to it.My question is: How can I add a delegate so that the you can set it as the default cmd+drag behaviour of XCode?
My current attempt is by doing something like this:
In my .h file:
#import <UIKit/UIKit.h>
IB_DESIGNABLE
#interface CustomTextField : UIView
#property (assign, nonatomic) IBInspectable id textFieldDelegate;
#end
and my .m file:
#import "CustomTextField.h"
#interface CustomTextField()
#property (strong, nonatomic) IBOutlet UITextField *textField;
#end
#implementation CustomTextField
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
[self loadNib];
}
return self;
}
- (void)loadNib{
UIView *view = [[[NSBundle bundleForClass:[self class]] loadNibNamed:#"CustomTextField" owner:self options:nil] firstObject];
[self addSubview:view];
view.frame = self.bounds;
}
- (void)setTextFieldDelegate:(id)textFieldDelegate{
self.textField.delegate = textFieldDelegate;
}
#end
But it doest show up in the left panel of XCode, Connections Inspector Tab.
Also here is my current code.
Update 1:
also if using the code below I get a an error:
- (id)initWithFrame:(CGRect)aRect
{
self = [super initWithFrame:aRect];
if (self)
{
[self loadNib];
}
return self;
}
- (id)initWithCoder:(NSCoder*)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self loadNib];
}
return self;
}
- (void)loadNib{
//
// UIView *view = [[[NSBundle bundleForClass:[self class]] loadNibNamed:#"CustomTextField" owner:self options:nil] firstObject];
// [self addSubview:view];
// view.frame = self.bounds;
UIView *view = [[[NSBundle bundleForClass:[self class]] loadNibNamed:#"CustomTextField" owner:self options:nil] firstObject];
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:view];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)]];
self.textLabel.text = #"1";
}
Any suggestions on what should I do?
Update 2
I ended up by replacing the initWithFrame, initWithCoder and loadNib functions with:
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder
{
if (![self.subviews count])
{
NSBundle *mainBundle = [NSBundle mainBundle];
NSArray *loadedViews = [mainBundle loadNibNamed:#"CustomTextField" owner:nil options:nil];
CustomTextField *loadedView = [loadedViews firstObject];
loadedView.frame = self.frame;
loadedView.autoresizingMask = self.autoresizingMask;
loadedView.translatesAutoresizingMaskIntoConstraints =
self.translatesAutoresizingMaskIntoConstraints;
for (NSLayoutConstraint *constraint in self.constraints)
{
id firstItem = constraint.firstItem;
if (firstItem == self)
{
firstItem = loadedView;
}
id secondItem = constraint.secondItem;
if (secondItem == self)
{
secondItem = loadedView;
}
[loadedView addConstraint:
[NSLayoutConstraint constraintWithItem:firstItem
attribute:constraint.firstAttribute
relatedBy:constraint.relation
toItem:secondItem
attribute:constraint.secondAttribute
multiplier:constraint.multiplier
constant:constraint.constant]];
}
return loadedView;
}
return self;
}
I have the following layout.
Green and Orange views are extra views added to the view controller.
I want to change the contained view controllers view to be changed according to the button user clicked.
I have Answer Using UIContainer dynamically show two ViewControllers in ios,
I have Create BaseViewController and Two viewControllers(GreenViewController and OrangeViewController), let seen below image are
And baseView inserted two UIButton(Green and Orange button), UIContainerView and the source is below,
BaseViewController.h file:
#property (weak, nonatomic) IBOutlet UIView *containView;
#property (weak, nonatomic) UIViewController *currentViewController;
BaseViewController.m file:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_currentViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"GreenViewController"];
_currentViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
[self addChildViewController:_currentViewController];
[self addSubview:_currentViewController.view toView:_containView];
}
- (void)addSubview:(UIView *)subView toView:(UIView*)parentView {
[parentView addSubview:subView];
NSDictionary * views = #{#"subView" : subView,};
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[subView]|"
options:0
metrics:0
views:views];
[parentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[subView]|"
options:0
metrics:0
views:views];
[parentView addConstraints:constraints];
}
- (void)cycleFromViewController:(UIViewController*) oldViewController
toViewController:(UIViewController*) newViewController {
[oldViewController willMoveToParentViewController:nil];
[self addChildViewController:newViewController];
[self addSubview:newViewController.view toView:self.containView];
[newViewController.view layoutIfNeeded];
// set starting state of the transition
newViewController.view.alpha = 0;
[UIView animateWithDuration:0.5
animations:^{
newViewController.view.alpha = 1;
oldViewController.view.alpha = 0;
}
completion:^(BOOL finished) {
[oldViewController.view removeFromSuperview];
[oldViewController removeFromParentViewController];
[newViewController didMoveToParentViewController:self];
}];
}
Green Button Action is below
- (IBAction)greenViewAction:(id)sender {
UIViewController *newViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"GreenViewController"];
newViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
[self cycleFromViewController:self.currentViewController toViewController:newViewController];
self.currentViewController = newViewController;
}
Orange Button Action is below
- (IBAction)orangeViewAction:(id)sender {
UIViewController *newViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"OrangeViewController"];
newViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
[self cycleFromViewController:self.currentViewController toViewController:newViewController];
self.currentViewController = newViewController;
}
its working for me, see the output below,
hope its helpful
I have the following UIView hierarchy:
-UIView
-UIScrollView
My constraint for UIScrollview with relation to it's super view are very simple:
#"H:|-%f-[%#]-%f-|"
and
#"V:|-%f-[%#]-%f-|"
They are working as expected.
I am trying to add a UIImageView as subview of scrollview Horizontal.
So my view hierarchy will become:
-UIView
-UIScrollView
-UIImageView
I am adding UIImageView as subview programmatically in UIScrollView using a for loop.
In the for loop, how can I achieve:
[SuperView]-10-[scrollview]-10-[UIImageView]-10-[UIImageView]-10-[UIScrollView]-10-[SuperView]
The problematic section is the bold part.
What I have tried:
for(int i=1;i<3;i++)
{
UIImageView *image = [[UIImageView alloc] init];
[image setImage:[UIImage imageNamed:[NSString stringWithFormat:#"%d.jpg",i]]];
image.translatesAutoresizingMaskIntoConstraints = NO;
[_scrollView addSubview:image];
UIView *superView = _scrollView;
NSDictionary * views = NSDictionaryOfVariableBindings(superView, image);
NSString *formate = [NSString stringWithFormat:#"H:|-%f-[%#]-%f-|", scrollViewLeftMarginFromParent, #"image", scrollViewRightMarginFromParent];
NSArray * WIDTH_CONSTRAINT = [NSLayoutConstraint constraintsWithVisualFormat:formate options:0 metrics:nil views:views];
formate = [NSString stringWithFormat:#"V:|-%f-[%#]-%f-|", scrollViewTopMarginFromParent, #"image", scrollViewBottomMarginFromParent];
NSArray * HEIGHT_CONSTRAINT = [NSLayoutConstraint constraintsWithVisualFormat:formate options:0 metrics:nil views:views];
[superView addConstraints:WIDTH_CONSTRAINT];
[superView addConstraints:HEIGHT_CONSTRAINT];
}
The approach I can think of:
LeftSide:
[scrollview]-10-[UIImageView]
Right side:
[UIImageView]-10-[scrollview]
in between:
[UIImageView]-10-[UIImageView]
If it's the right approach, then how do I achieve this in for loop.
If it's not then what is best approach.
It's quite simple actually. Your approach is correct, all you need is how you convert that into code. I will try to simplify this for you. I am assuming a UIImageView's width & height as 100. You can change as you like
-(void)setUI
{
lastView = nil; //Declare a UIImageView* as instance var.
arrayCount = [array count]; //In your case a static count of 3
for(NSInteger index =0; index < arrayCount; index++)
{
UIImageView *view = [[UIImageView alloc] init];
[self.mainScroll addSubview:view];
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.mainScroll addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-20-[view(100)]-20-|" options:0 metrics:nil views:#{#"view":view}]];
//--> If view is first then pin the leading edge to main ScrollView otherwise to the last View.
if(lastView == nil && index == 0) {
[self.mainScroll addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-10-[view(100)]" options:0 metrics:nil views:#{#"view":view}]];
}
else {
[self.mainScroll addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[lastView]-10-[view(100)]" options:0 metrics:nil views:#{#"lastView":lastView, #"view":view}]];
}
//--> If View is last then pin the trailing edge to mainScrollView trailing edge.
if(index == arrayCount-1) {
[self.mainScroll addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[view]-10-|" options:0 metrics:nil views:#{#"view":view}]];
}
//--> Assign the current View as last view to keep the reference for next View.
lastView = view;
}
}
I had encountered similar situation where my scrollview along with its content view was created from IB, but the subviews were added programatically. Writing constraints for subviews was making the View controller bloated. Also the for loop was was getting a lots of ifs and elses,hence I wrote a UIView Subclass to handle this scenario.
Change the class type for you Content View in IB, get a reference of it, add subviews through directly setting the property stackViewItems,or methods -(void)insertStackItem:, -(void)insertStackItem:atIndex:
#import "IEScrollContentView.h"
#interface IEScrollContentView()
{
NSMutableArray * _stackViewItems;
}
#property (nonatomic,strong) NSLayoutConstraint * topConstraint;
#property (nonatomic,strong) NSLayoutConstraint * bottomConstraint;
#end
#implementation IEScrollContentView
#synthesize stackViewItems = _stackViewItems;
//-----------------------------------------------------------------//
#pragma mark - Init Methods
//-----------------------------------------------------------------//
-(instancetype)initWithCoder:(NSCoder *)aDecoder {
if(self = [super initWithCoder:aDecoder])
_stackViewItems = [NSMutableArray new];
return self;
}
-(instancetype)initWithFrame:(CGRect)frame {
if(self = [super initWithFrame:frame])
_stackViewItems = [NSMutableArray new];
return self;
}
//-----------------------------------------------------------------//
#pragma mark - Public Methods
//-----------------------------------------------------------------//
-(void)setStackViewItems:(NSArray *)stackViewItems {
if(!_stackViewItems)
_stackViewItems = [NSMutableArray new];
for (UIView * view in stackViewItems) {
[self insertStackItem:view];
}
}
-(void)insertStackItem:(UIView *)stackItem
{
[self insertStackItem:stackItem atIndex:_stackViewItems.count];
}
-(void)insertStackItem:(UIView *)stackItem atIndex:(NSUInteger)index
{
if(!stackItem || index > _stackViewItems.count)return;
if(index == 0)
[self addView:stackItem
belowView:self
aboveView:_stackViewItems.count>0?_stackViewItems.firstObject:self];
else if(index==_stackViewItems.count)
[self addView:stackItem
belowView:_stackViewItems[index-1]
aboveView:self];
else
[self addView:stackItem
belowView:_stackViewItems[index-1]
aboveView:_stackViewItems[index]];
}
//-----------------------------------------------------------------//
#pragma mark - Constraining Views
//-----------------------------------------------------------------//
-(void)addView:(UIView *)view belowView:(UIView *)viewAbove aboveView:(UIView *)viewBelow {
view.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:view];
NSArray * defaultConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[view]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)];
NSLayoutConstraint * upperConstraint,* lowerConstraint;
if(viewAbove==self) {
[self removeConstraint:_topConstraint];
upperConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[view]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)].firstObject;
_topConstraint = upperConstraint;
}
else
upperConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[viewAbove]-0-[view]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view,viewAbove)].firstObject;
if(viewBelow==self) {
[self removeConstraint:_bottomConstraint];
lowerConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[view]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)].firstObject;
_bottomConstraint = lowerConstraint;
}
else
lowerConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[view]-0-[viewBelow]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view,viewBelow)].firstObject;
[self addConstraints:defaultConstraints];
[self addConstraints:#[upperConstraint,lowerConstraint]];
[_stackViewItems addObject:view];
}
#end
I have uploaded the files here
IEScrollContentView.h
IEScrollContentView.h.m
I'm working on a project that must support both iOS 8 and iOS 7.1. Right now I'm running into a problem that only appears on iOS 7.1 but works properly on iOS 8. I have a ViewController that contains a tableview and a custom view in the tableHeaderView. I'll post the code as follows. All constraints are added programatically.
//View Controller.
-(void)viewDidLoad
{
[super viewDidLoad];
self.commentsArray = [NSMutableArray new];
[self.commentsArray addObject:#"TEST"];
[self.commentsArray addObject:#"TEST"];
[self.commentsArray addObject:#"TEST"];
[self.commentsArray addObject:#"TEST"];
[self.commentsArray addObject:#"TEST"];
[self.commentsArray addObject:#"TEST"];
[self.view addSubview:self.masterTableView];
self.masterTableView.tableHeaderView = self.detailsView;
[self.view setNeedsUpdateConstraints];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.view layoutIfNeeded];
}
//Table view getter
- (UITableView *)masterTableView
{
if(!_masterTableView)
{
_masterTableView = [UITableView new];
_masterTableView.translatesAutoresizingMaskIntoConstraints = NO;
_masterTableView.backgroundColor = [UIColor greyColor];
_masterTableView.delegate = self;
_masterTableView.dataSource = self;
_masterTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
_masterTableView.showsVerticalScrollIndicator = NO;
_masterTableView.separatorInset = UIEdgeInsetsZero;
}
return _masterTableView;
}
-(void)updateViewConstraints
{
NSDictionary *views = #{
#"table" : self.masterTableView,
#"details" : self.detailsView,
};
NSDictionary *metrics = #{
#"width" : #([UIScreen mainScreen].applicationFrame.size.width)
};
//Table view constraints
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[table]-0-|" options:0 metrics:0 views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[table]-0-|" options:0 metrics:0 views:views]];
//Details View
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[details(width)]-0-|" options:0 metrics:metrics views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[details]-0-|" options:0 metrics:metrics views:views]];
}
//Details View Getter
- (DetailsView *)detailsView
{
if(!_detailsView)
{
_detailsView = [[DetailsView alloc]initWithFrame:CGRectMake(0, 0, 0, 100)];
_detailsView.backgroundColor = [UIColor orangeColor];
return _detailsView;
}
Now the details view contains some basic subviews which all derive from a UIView and the details view itself derives from a more general super class. I'll post the code as follows.
//Parent View
#interface ParentView (): UIView
- (instancetype)init
{
self = [super init];
if (self)
{
[self setupViews];
}
return self;
}
- (void)setupViews
{
[self addSubview:self.publishedTimeView];
}
- (void)updateConstraints
{
NSDictionary *views = #{
#"time" : self.publishedTimeView,
};
// Header with published video time
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[time]-10-|" options:0 metrics:0 views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[time(11)]" options:0 metrics:0 views:views]];
[super updateConstraints];
}
//Getter for the timePublished view added to the detail view. Will not post all its related code for //the sake of brevity.
- (TimePublishedDetailView *)publishedTimeView
{
if(!_publishedTimeView)
{
_publishedTimeView = [TimePublishedDetailView new];
_publishedTimeView.translatesAutoresizingMaskIntoConstraints = NO;
}
return _publishedTimeView;
}
//Child View (or the detailsView) of the view controller.
#interface DetailsView : ParentView
#implementation RecordingDetailsView
- (void)updateConstraints
{
NSDictionary *views = #{
#"time" : self.publishedTimeView,
};
//Vertical alignment of all views
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-20-[time]" options:0 metrics:nil views:views]];
[super updateConstraints];
}
- (void)setModel:(DetailViewModel *)model
{
self.publishedTimeView.date = model.dateTime;
[self setNeedsUpdateConstraints];
[self layoutSubviews];
}
Now on iOS 8 this looks like this:
However on iOS 7.1 this will crash with this error message:
"Exception: Auto layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super"
I've googled this and played around with the code in my layout calls to see if I can remedy the problem but so far have been unsuccessful. If anyone could post some tips or advice on how to fix this I would really appreciate it.
I know this question is old, but I ran into a similar problem recently and after a lot of Googling, trying and failing I made it work with the help of this answer and a few changes.
Just add this category to your project and call [UITableView fixLayoutSubviewsMethod]; only once (I recommend inside AppDelegate).
#import <objc/runtime.h>
#import <objc/message.h>
#implementation UITableView (FixUITableViewAutolayoutIHope)
+ (void)fixLayoutSubviewsMethod
{
Method existing = class_getInstanceMethod(self, #selector(layoutSubviews));
Method new = class_getInstanceMethod(self, #selector(_autolayout_replacementLayoutSubviews));
method_exchangeImplementations(existing, new);
}
- (void)_autolayout_replacementLayoutSubviews
{
[super layoutSubviews];
[self _autolayout_replacementLayoutSubviews]; // not recursive due to method swizzling
[super layoutSubviews];
}
#end
I have an horizontal scroll view on which i add views dynamically.
On LTR languages everything work fine, i add views one after the other from left to right.
On RTL the problem is that the views always added to the left of the scroll instead of to the right like in every other controller, the really strange staff that the order of the views is added correctly, to the left of the first view so they are ordered from right to left but outside of the scroll view on -x.
Here is my code when i add a new View:
Tag* tag = [self.storyboard instantiateViewControllerWithIdentifier:#"tag" ];
[_scroller addSubview:tag.view];
[tags addObject:tag];
Tag* prev = nil
for (Tag* tag in tags)
{
if (prev == nil)
{
[_scroller addConstraint:[NSLayoutConstraint constraintWithItem:tag.view
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:_scroller
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:0]];
}
else
{
[_scroller addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"[prev]-10-[tag]"
options:0
metrics:nil
views:#{#"tag" : tag.view, #"prev" : prev.view}]];
}
[_scroller addConstraint:[NSLayoutConstraint constraintWithItem:tag.view
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:_scroller
attribute:NSLayoutAttributeCenterY
multiplier:1.0f
constant:0]];
prev = tag;
}
Here is an image of how it suppose to work on LTR and RTL and how it actually works
The reason for this behavior of UIScrollView is that you forgot to attach the trailingAnchor of the last element (#4) to the scroll view's trailingAnchor.
The leadingAnchor of both the scroll view and element #1 are attached to each other (see below in green). The scroll view's content rect however naturally spans into the positive coordinate directions, from origin (0,0) to right, down (+x, +y). In your case the scroll view's content size is of width 0 because nothing is between scroll view's leadingAnchor and trailingAnchor.
So below your [_scroller addConstraints:_constraint]; add something like (pseudo code):
if tag == lastTag {
NSLAyoutconstraints.activate([
tag.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor)
])
}
It sounds like a better approach might be to use a UICollectionView. Then if you want to start it from the right side you could possibly do something like this:
NSIndexPath *lastIndex = [NSIndexPath indexPathForItem:data.count - 1
inSection:0];
[self.collectionView scrollToItemAtIndexPath:lastIndex
atScrollPosition:UICollectionViewScrollPositionRight
animated:NO];
This way the UICollectionViewFlowLayout can handle the placement for you.
Try this
#import "ViewController.h"
#interface ViewController ()<UIScrollViewDelegate>
{
UIView *baseView;
UILabel *titleLabel;
NSMutableArray *infoArray ;
UIScrollView *mainscrollview;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
infoArray =[[NSMutableArray alloc]initWithObjects:#"1",#"2",#"3", nil];
NSLog(#"%#",infoArray);
mainscrollview=[[UIScrollView alloc]initWithFrame:CGRectMake(0, 0, 320, 380)];
mainscrollview.delegate=self;
mainscrollview.contentSize=CGSizeMake(320*infoArray.count, 0);
[self.view addSubview:mainscrollview];
[self sscrollcontent:#"LTR"];//LTR for Lefttoright other than LTR it will show RTL
}
-(void)sscrollcontent:(NSString *)flowtype
{
int xaxis=0;
for (int i=0; i<infoArray.count; i++) {
baseView=[[UIView alloc]initWithFrame:CGRectMake(xaxis, 0, 320, 380)];
[mainscrollview addSubview:baseView];
titleLabel =[[UILabel alloc]initWithFrame:CGRectMake(0, 0, 320, 60)];
titleLabel.textAlignment=NSTextAlignmentCenter;
if ([flowtype isEqualToString:#"LTR"]) {
titleLabel.text=infoArray[i];
}
else
{
titleLabel.text=infoArray[infoArray.count-i-1];
}
[baseView addSubview:titleLabel];
xaxis=xaxis+320;
}
}
#end
Hope this will help you
This is my sample code.
//
// ViewController.m
// testConstraint
//
// Created by stevenj on 2014. 3. 24..
// Copyright (c) 2014년 Steven Jiang. All rights reserved.
//
#import "ViewController.h"
#interface TagView : UILabel
- (void)setNumber:(NSInteger)num;
#end
#implementation TagView
- (void)setNumber:(NSInteger)num
{
[self setText:[NSString stringWithFormat:#"%d",num]];
}
#end
#interface ViewController ()
#property (nonatomic, strong) UIScrollView *scroller;
#property (nonatomic, strong) NSMutableArray *tags;
#property (nonatomic, strong) NSMutableArray *constraint;
#end
#implementation ViewController
#synthesize scroller = _scroller;
- (void)viewDidLoad
{
[super viewDidLoad];
_tags = [NSMutableArray new];
_constraint = [NSMutableArray new];
// Do any additional setup after loading the view, typically from a nib.
//step.1 create scroll view
_scroller = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 20, 320, 60)];
[_scroller setBackgroundColor:[UIColor lightGrayColor]];
[_scroller removeConstraints:[_scroller constraints]];
[_scroller setTranslatesAutoresizingMaskIntoConstraints:YES];
[self.view addSubview:_scroller];
//step.2 add tag view
for (int i=0; i<10; i++) {
TagView *tag = [[TagView alloc] init];
[tag setFrame:CGRectMake(100, 30, 50, 30)];
[tag setNumber:i];
[tag.layer setBorderWidth:1.0];
[tag setTranslatesAutoresizingMaskIntoConstraints:NO];
[_scroller addSubview:tag];
[_tags addObject:tag];
}
//step.3 update contraints
[self myUpdateConstraints];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)myUpdateConstraints
{
[_constraint removeAllObjects];
TagView* prev = nil;
for (TagView* tag in _tags)
{
[tag setNumber:[_tags indexOfObject:tag]];
if (prev == nil)
{
[_constraint addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(<=300)-[tag]-20-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:#{#"tag" : tag}]];
}
else
{
[_constraint addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:#"[tag]-10-[prev]"
options:0
metrics:nil
views:#{#"tag" : tag, #"prev" : prev}]];
}
[_scroller addConstraint:[NSLayoutConstraint constraintWithItem:tag
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:_scroller
attribute:NSLayoutAttributeCenterY
multiplier:1.0f
constant:0]];
prev = tag;
}
[_scroller addConstraints:_constraint];
}
#end
Hopes it could help you.