I have a subclassed UITableViewCell, if I add a UIScrollView to cell's contentView, can't perform the segue, if I comment the line, it can perform the segue.
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self setup];
}
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self setup];
}
- (void)setup
{
UIScrollView *scrolView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds))];
scrolView.contentSize = CGSizeMake(CGRectGetWidth(self.bounds)+kButtonWidth, CGRectGetHeight(self.bounds));
scrolView.delegate = self;
scrolView.showsHorizontalScrollIndicator = NO;
// [self.contentView addSubview:scrolView];
}
You are adding a scroll view that is the full size of the cell in the content view, on top of all other views in the cell. It's covering them up, so no clicks get through. I suspect you want to add a scroll view as a container for your other views. I don't know how you could do that with all the standard views that the table view base class manages for you. You'd have to somehow override the behavior of the base class and change the content view into a scroll-view.
Alternatively you could do what you are doing, but then you would need to replace all the standard table view cell fields and behaviors with your own, which would be nearly impossible.
Adding a scroll view to a table view cell is probably a bad idea. The table view itself is a subclass of a scroll view, and it will be very hard/impossible to sort out which gestures are supposed to scroll the table view and which are supposed to scroll a cell. The only way I could see that working is if the cell's scroll view is limited to horizontal scrolling.
Related
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self setBackgroundColor:[UIColor clearColor]];
//[self createViews];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
NSLog(#"draw rect");
[self createViews];
}
I'm creating a custom UITableViewCell. I require creating a UILabel that depends on the height of the UITableViewCell, and the height is not yet set in initWithStyle (it returns the default 44 when in reality the height of my cell varies greatly). For this reason, I call my createViews function in drawRect. This was working well, however I'm noticing that the function can be called again when I insert and delete rows.
My Question:
Does it make sense to call my createViews function inside drawRect?
You have few options here.
1. Use layoutSubviews/awakeFromNib, check whether the subviews were created, if no, create them with correct frames.
2. Use init to create views with:
Constraints
Without constraints and in layoutSubviews/awakeFromNib try to change the frame
I'm using PureLayout to implement AutoLayout of subviews in a UIView. But I don't know the best practice of organizing the code.
Should I put the AutoLayout related code in the init of the UIView, or the overridden methods such as updateConstraints and layoutSubviews?
For example, I want to create a subclass of UIView called PHView, and for any phview, there is a subview called centerView, it is always at the center of phview, and width/height is 0.3*phview's width/height.
https://www.dropbox.com/s/jaljggnymxliu1e/IMG_3178.jpg
#import "PHView.h"
#import "Masonry.h"
#interface PHView()
#property (nonatomic, assign) BOOL didUpdateConstraints;
#property (nonatomic, strong) UIView *centerView;
#end
#implementation PHView
- (instancetype)init {
self = [super init];
if (self) {
self.backgroundColor = [UIColor redColor];
self.translatesAutoresizingMaskIntoConstraints = NO;
}
return self;
}
- (UIView *)centerView {
if (!_centerView) {
_centerView = [UIView new];
_centerView.backgroundColor = [UIColor yellowColor];
[self addSubview:_centerView];
}
return _centerView;
}
-(void)updateConstraints {
if (!_didUpdateConstraints) {
_didUpdateConstraints = YES;
[self.centerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.mas_centerX);
make.centerY.equalTo(self.mas_centerY);
make.width.equalTo(self.mas_width).multipliedBy(0.3);
make.height.equalTo(self.mas_height).multipliedBy(0.3);
}];
}
[super updateConstraints];
}
#end
'didUpdateConstraints' aims to indicate you have added constraints, so you will only add constraints once.
in UIViewController:make phview top bottom left right 20 to the margin.
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor greenColor];
PHView *myView = [PHView new];
[self.view addSubview:myView];
[myView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(20, 20, 20, 20));
}];
}
You should add constraints when you are sure that view has been added to its superview. Basically, you should do it in superview's class any point after addSubview: is called.
To answer your questions:
1- in init methods, can you be sure of that view has been added as subview to a superview? it wouldn't be safe to assume that. maybe you can add constraints in init method of superview
2- layoutSubviews is in where autolayout code actually works. you can't add constraints in layoutSubviews. already playing with autolayout constraints are not cheap, therefore you should add/remove them as few as possible, doing so in a method that is called multiple times (i.e. layoutSubviews) is not the best practice.
Mechanism of autolayout is going to inner view from outer view, so subviews do not actually concern about constraints. it is superview's responsibility
Hope this helps you by understanding controller’s view hierarchy
How View Controllers Participate in the View Layout Process
The view controller’s view is resized to the new size.
If autolayout is not in use, the views are resized according to their autoresizing masks.
The view controller’s viewWillLayoutSubviews method is called.
The view’s layoutSubviews method is called. If autolayout is used to configure the view hierarchy, it updates the layout constraints by executing the following steps:
a.The view controller’s updateViewConstraints method is called.
b.The UIViewController class’s implementation of the updateViewConstraints method calls the view’s updateConstraints method.
c. After the layout constraints are updated, a new layout is calculated and the views are repositioned.
The view controller’s viewDidLayoutSubviews method is called.
Please refer this for more details
Using Storyboards and Autolayout, I have a UIViewController with a UIScrollView as the main view. I have several container views embedded in the scroll view. Some of those embedded container views contain UITableViews, each having cells of different heights. I'll need the tableView's height to be large enough to show all cells at once, as scrolling will be disabled on the tableView.
In the main UIViewController, container view's height has to be defined in order for the scroll view to work properly. This is problematic because there's no way for me to know how large my tableView will be once all it's cells of varying heights are finished rendering. How can I adjust my container view's height at runtime to fit my non-scrolling UITableView?
So far, I've done the following:
// in embedded UITableViewController
//
- (void)viewDidLoad {
// force layout early so I can determine my table's height
[self.tableView layoutIfNeeded];
if (self.detailsDelegate) {
[self.detailsTableDelegate didDetermineHeightForDetailsTableView:self.tableView];
}
}
// in my main UIViewController
// I have an IBOutlet to a height constraint set up on my container view
// this initial height constraint is just temporary, and will be overridden
// once this delegate method is called
- (void)didDetermineHeightForDetailsTableView:(UITableView *)tableView
{
self.detailsContainerHeightConstraint.constant = tableView.contentSize.height;
}
This is working fine and I was pleased with the results. However, I have one or two more container views to add, which will have non-scrolling tableViews, and I'd hate to have to create a new delegate protocol for each container view. I don't think I can make the protocol I have generic.
Any ideas?
Here's what I ended up doing:
// In my embedded UITableViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 60.0;
// via storyboards, this viewController has been embeded in a containerView, which is
// in a scrollView, which demands a height constraint. some rows from our static tableView
// might not display (for lack of data), so we need to send our table's height. we'll force
// layout early so we can get our size, and then pass it up to our delegate so it can set
// the containerView's heightConstraint.
[self.tableView layoutIfNeeded];
self.sizeForEmbeddingInContainerView = self.tableView.contentSize;
}
// in another embedded view controller:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.sizeForEmbeddingInContainerView = self.tableView.contentSize;
}
// then, in the parent view controller, I do this:
// 1) ensure each container view in the storyboard has an outlet to a height constraint
// 2) add this:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.placeDetailsContainerHeightConstraint.constant = self.placeDetailsTableViewController.sizeForEmbeddingInContainerView.height;
self.secondaryExperiencesContainerHeightConstraint.constant = self.secondaryExperiencesViewController.sizeForEmbeddingInContainerView.height;
}
I haven't done this yet, but it'd probably be best to create a Protocol with a property of CGSize sizeForEmbeddingInContainerView that each child view controller can adopt.
Here's what worked for me perfectly.
- (void)updateSizeBasedOnChildViews {
// Set height of container to match embedded tableview
CGRect containerFrame = self.cardTableContainer.frame;
containerFrame.size.height = [[[self.cardTableContainer subviews] lastObject]contentSize].height;
self.cardTableContainer.frame = containerFrame;
// Set content height of scrollview according to container
CGRect scrollFrame = self.cardTabScrollView.frame;
scrollFrame.size.height = containerFrame.origin.y + containerFrame.size.height;
// + height of any other subviews below the container
self.cardTabScrollView.contentSize = scrollFrame.size;
}
I was trying to implement a view like twitter's iOS app. The view controller contains a scroll view can scroll horizontally, in each page of the scrollview, there is a tableview. For simplicity I just created two UITableView, and add them inside two UIView containers, because besides tableView, I also have some view to show, so the tableView and all other views are added as subViews of the viewContainer for each page.
The view hierarchy is like this:
UIViewController
--view
--scrollView
--view1
--tableView1
--someOtherViews
--view2
--tableView2
--someOtherViews
In the viewDidLoad methods, I implemented something like this:
-(void)viewDidLoad
{
//Some other setup...
scrollView = [[UIScrollView alloc]initWithFrame(0,y0,self.view.frame.size.width,self.view.frame.size.height-offset)];
scrollView.pageEnabled = YES;
scrollView.contentSize = CGSizeMake(self.view.frame.size.width * 2, self.view.frame.size.height - offset);
[self.view addSubView:scrollView];
view1 = [[UIView alloc]initWithFrame:CGRectMake(0,0,self.view.frame.size.width,self.view.frame.size.height-offset)];
tableView1 = [[UITableView alloc]initWithFrame:CGRectMake(0,0,self.view.frame.size.width,self.view.frame.size.height-offset) style:UITableViewStyleGrouped];
tableView1.delegate = self;
tableView1.dataSource = self;
//other setup...
[view1 addSubView:tableView1];
[scrollView addSubView:view1];
view2 = [[UIView alloc]initWithFrame:CGRectMake(self.view.frame.size.width,0,self.view.frame.size.width,self.view.frame.size.height-offset)];
tableView2 = [[UITableView alloc]initWithFrame:CGRectMake(0,0,self.view.frame.size.width,self.view.frame.size.height-offset) style:UITableViewStyleGrouped];
tableView2.delegate = self;
tableView2.dataSource = self;
//other setup...
[view2 addSubView:tableView2];
[scrollView addSubView:view2];
}
For test purpose, I populate the same datasource for both tables. The problem is if I only add one tableView container (for instance view1, which includes tableView1) as the subView of the scrollView, the view shows perfect, one can page to second page, which is blank, and the view controller can be load and show. The same for the second one.
However, if I use the code above, which adds both view containers as the subView of the scrollView, the app would crash when load this view controller. However, if I add the second view container (view2 or view1), but in the view container, I am not adding tableView as its subView, but has other subViews, it works perfectly.
So I figured the problem probably comes from two tableViews. However, I have tried to debug this for a few hours, and still no luck. Anyone can help me?
Thanks!!
I think it is something with you UITableViewDelegate or DataSource methods.
Probably it is the reason you crash.
-Suggestion:
try to make your UITableViews as an iVar and in dataSource methods
specify the tableView you want
like:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section
{
if(tableView == _tableView1)
return _table1DataSource.count;
else
return _table2DataSource.count;
}
try to do it for other methods.
I want to add a view to the bottom of the content view of both a collection view and table view (and hence is applicable to any kind of scroll view) and I also want to be able to scroll down to see this view e.g.:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// Observe change in content size so can move my view when
// content size changes (keep it at the bottom)
[self addObserver:self forKeyPath:#"contentSize"
options:(NSKeyValueObservingOptionPrior)
context:nil];
CGRect frame = CGRectMake(0, 0, 200, 30);
self.loadingView = [[UIView alloc] initWithFrame:frame];
[self.loadingView setBackgroundColor:[UIColor blackColor]];
[self addSubview:self.loadingView];
// Increase height of content view so that can scroll to my view.
self.contentSize = CGSizeMake(self.contentSize.width, self.contentSize.height+30);
}
return self;
}
However when, for example, a cell is inserted the contentSize is recalculated and whilst my view is still visible at the bottom of the content size (due to being able to bounce the scroll view) I can no longer scroll to it.
How do I ensure that the content size stays, as in my code, 30 points taller?
An additional question is:
is there any other way to track content size other than observing it?
Thanks in advance.
I have tried:
- (void)layoutSubviews
{
[super layoutSubviews];
self.contentSize = CGSizeMake(self.contentSize.width, self.contentSize.height+30);
}
However this causes all sorts of display issues.
If i understand correctly, you want to show a loading view in the tableView (f.e.) at the bottom. You could add an extra UITableViewCell containing this LoaderView to the tableView.
(Must change the numberOfRowsInTableView)
In another perspective for scrollViews: Use smaller bounds then the content itself, to make it scrollable. For example frame = fullscreen. At every cell adding or modification in subviews (adding) contentSize = content size + 30 px.
Try making a subclass of the scroll view and override the contentSize getter to return always 30 px more.
- (CGSize)contentSize {
CGSize customContentSize = super.contentSize;
customContentSize.height += 30;
return customContentSize;
}
(I'm writing the code by memory, there may be errors)