I have created a subclass of NSTextField that changes its height according to the text it contains. I now want to insert it in another view (an NSTableCellView) and make the view resize according to the height of the text field.
I want to use the -(NSSize)fittingSize method of NSView but unfortunately it doesn't seem to call the fittingSize method of its subviews nor their intrinsicContentSize method.
Here is the code I use for the NSTableCellView subclass:
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
self.expandingTextField = [[JSExpandingTextField alloc] init];
[self addSubview:self.expandingTextField];
[self removeConstraints:self.constraints];
NSDictionary *row = NSDictionaryOfVariableBindings(expandingTextField);
[self addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|-20-[expandingTextField]-28-|"
options:0 metrics:nil views:row]];
}
return self;
}
- (NSSize)fittingSize
{
return [super fittingSize];
}
I override the fittingSize method here only to put a breakpoint or an NSLog.
Here is the code of the table view delegate that provides the height of the table cell:
- (JSDynamicTableCellView *)dummyCell
{
if (!_dummyCell) {
_dummyCell = [[JSDynamicTableCellView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 100, 100)];
}
return _dummyCell;
}
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
{
self.dummyCell.expandingTextField.stringValue = #"Test";
NSLog(#"Cell size %#",NSStringFromSize([self.dummyCell fittingSize]));
return [self.dummyCell fittingSize].height;
}
All of this always returns an height for dummyCell of 69 independent of the size of the expanding textfield in the cell.
The question is: how does the 'fittingSize' method figure out the size of its subviews? Should it call their 'fittingSize' or 'ntrinsicContentSize' methods or is it something else?
fittingSize is conceptually simple. It collects all of the constraints that have been added to your view or one of its subviews (recursively), and then it determines the size of the view based on only those constraints. You can think of it as determining the smallest size that is big enough to show the contents of that view hierarchy.
Edit:We need to be quite clear here. fittingSize returns minimum values and will return 0 for any dimension that is not fully specified. For example, if the vertical constraint tying a view to the bottom of its superview is omitted then the fitted height will be 0.
Edit: I just realized what you're probably running into: fittingSize is computing the text field as if it were just one long line, that does not wrap. You can confirm this by giving it a string value with one or more newlines: now the height should be bigger!
So how to fix this? Well, you have a text field, and you give it contents, and you want to know its height. But if the text field wraps, the height is going to depend on the width: make it narrower, and the text field will wrap to more lines, so it will consume more height. So in order to measure the preferred height for a wrapping text field, you have to tell it the width to wrap at.
On OS X Mountain Lion and iOS 6, you can do that with the [NSTextField setPreferredMaxLayoutWidth:] method. For example, if you want to compute the height based on a width of 100, you would call [textField setPreferredMaxLayoutWidth:100]; now fittingSize will report a height for the text field based on it wrapping at a width of 100.
By the way, this is a bad idea:
[self removeConstraints:self.constraints];
Because it removes constraints that other parts of the system have added. You should only ever remove a constraint that you created, either in code or in IB.
Try this (to do this click on the background of the xib or storyboard)
Related
My UICollectionView cells contain UILabels with multiline text. I don't know the height of the cells until the text has been set on the label.
-(CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
This was the initial method I looked at to size the cells. However, this is called BEFORE the cells are created out of the storyboard.
Is there a way to layout the collection and size the cells AFTER they have been rendered, and I know the actual size of the cell?
I think your are looking for the invalidateLayout method you can call on the .collectionViewLayout property of your UICollectionView. This method regenerates your layout, which in your case means also calling -collectionView: layout: sizeForItemAtIndexPath:, which is the right place to reflect your desired item size. Jirune points the right direction on how to calculate them.
An example for the usage of invalidateLayout can be found here. Also consult the UICollectionViewLayout documentation on that method:
Invalidates the current layout and triggers a layout update.
Discussion:
You can call this method at any time to update the layout information. This method invalidates the layout of the collection view itself and returns right away. Thus, you can call this method multiple times from the same block of code without triggering multiple layout updates. The actual layout update occurs during the next view layout update cycle.
Edit:
For storyboard collection view which contains auto layout constraints, you need to override viewDidLayoutSubviews method of UIViewController and call invalidateLayout collection view layout in this method.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[yourCollectionView.collectionViewLayout invalidateLayout];
}
subclass UICollectionViewCell and override layoutSubviews like this
hereby you will anchor cell leading and trailing edge to collectionView
override func layoutSubviews() {
super.layoutSubviews()
self.frame = CGRectMake(0, self.frame.origin.y, self.superview!.frame.size.width, self.frame.size.height)
}
Hey in the above delegate method itself, you can calculate the UILabel size using the below tricky way and return the UICollectionViewCell size based on that calculation.
// Calculate the expected size based on the font and
// linebreak mode of your label
CGSize maximumLabelSize = CGSizeMake(9999,9999);
CGSize expectedLabelSize =
[[self.dataSource objectAtIndex:indexPath.item]
sizeWithFont:[UIFont fontWithName:#"Arial" size:18.0f]
constrainedToSize:maximumLabelSize
lineBreakMode:NSLineBreakByWordWrapping];
- (void)viewDidLoad {
self.collectionView.prefetchingEnabled = NO;
}
In iOS 10, prefetchingEnabled is YES by default. When YES, the collection view requests cells in advance of when they will be displayed. It leads to crash in iOS 10
I want to change the titleView of navigationBar with a regular text field. Then I want to set the textField size to fill the old "normal" titleView.
I can't seem to be able to do this in storyBoard. Dragging a text Field to the place where the navigation title View is doesn't work.
So I added stuff at
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
PO(self.navigationItem.titleView);
CGRect theFrame= self.navigationItem.titleView.frame;
self.navigationItem.titleView=self.searchBar;
//self.searchBar.frame = theFrame;
while (false);
...
It's working with one cached. That PO is a macro that print the content of the object. Turns out at viewDidAppear, self.navigationItem.titleView is null.
So while I can display the searchBar, I cannot make the searchBar "fill" it's space because I do not know the space is.
I prefer not to hard code it because you know, things may change in the future.
So what should I do?
I once saw codes where rather than setting the self.navigationItem.titleView, you would simply add subview to it. The problem with this approach even on viewDidAppear, self.navigationItem.titleView is 0.
I added these codes:
CGRect theFrame= self.navigationItem.titleView.frame;
CGRect theFrame2 = self.searchBar.frame;
CGRect theFrame3 = self.navigationController.navigationItem.titleView.frame;
And, I do not know how to nslog structure value, however, theFrame and theFrame3 are all 0
You can try this inside viewWillAppear:
UIView *customTitleView = [[UIView alloc]initWithFrame:CGRectMake((320-210)/2, 0, 210, 50)];
customTitleView.backgroundColor = [UIColor clearColor];
//create your UITextField or UILabel or other view and add as subview of customTitleView
self.navigationItem.titleView = customTitleView;
I have 2 tables in one view. Table A lists a bunch of users. Table B lists a users objects. When a row is selected in Table A, Table B is reloaded with the objects that belong to that user.
So when a user selects a row in Table A, the image in the background of the cell changes to the highlighted version of the image.
Here is the normal version of the background image:
Here is the highlighted version of the background image:
As you can see, the highlighted version has a small arrow on the right of it. This arrow is beyond the width of the table cell the table itself. When the row is selected, the image changes as it should, but the image is sized down to fit the whole image into the cell.
What I would like to happen is the image goes outside of the table, or on top of the table for that selected row.
One possible solution I thought was to center the table on the selected row and then overlay that image, but if the user was to try to scroll through the table, the image would need to move and that would be a big pain.
So what I would like to know is it is possible to extend the cell's size beyond the table one it is selected?
Thanks in advance!
EDIT:
The following does not work, just in case anyone was going to try:
[cell setFrame:CGRectMake(cell.frame.origin.x, cell.frame.origin.y, cell.frame.size.width+20, cell.frame.size.height)];
Setting a views clipsToBounds property to NO will allow the view to draw outside of its own frame.
In your UITableViewController subclass:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do what you normally do here, if anything then add...
self.view.clipsToBounds = NO;
}
Doing this has a side effect where you will see full cells be created at the bottom or top of the tableview instead of them scrolling partially into view. Either put the table view into another view that has clipsToBounds set to YES, align the edges of the table view with the edges of the screen, or have views covering over the bottom and top (like a UIToolbar and UINavigationBar normally would).
To get the UITableViewCell's selectedBackgroundView to extend past the table view's frame create a subclass of UITableViewCell and override the layoutSubviews method.
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
self.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"YQGyZ"]];
self.selectedBackgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"CQXYh"]];
self.textLabel.backgroundColor = [UIColor clearColor];
self.detailTextLabel.backgroundColor = [UIColor clearColor];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGRect frame = self.selectedBackgroundView.frame;
frame.size.width += 13; // where 13 is the size of the arrow overhang from the same images
self.selectedBackgroundView.frame = frame;
// You can also change the location of the default labels (set their background colors to clear to see the background under them)
self.textLabel.frame = CGRectMake(70, 0, 148, 30);
self.detailTextLabel.frame = CGRectMake(70, 30, 148, 30);
}
Good luck.
I would recommend modifying the image resource of the unselected cell background such that it is the same width as the selected background, but just has a transparent rectangle on the side.
I have a UIPopoverController in my app which simply displays two UILabels beside each other with a list of words in each of them. However sometimes there are only a couple of words in each list meaning there is tons of blank space in the popover view.
How can I make it so that the popover view in at least height dynamically adapts to how many lines of words there are in my label?
Any help always appreciated, thanks.
If text in label is specified before popover shows, you can achieve this by using similar code in viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
// ...
CGFloat height = [label.text sizeWithFont:label.font
forWidth:label.frame.size.width
lineBreakMode:label.lineBreakMode].height;
// This calculates only height of the label, you may want to add some margins, etc.
CGSize size = CGSizeMake(self.view.frame.size.width, height);
self.contentSizeForViewInPopover = size;
}
I have a subclass of UILabel, which is supposed to update its text when the user types something. Naturally, as the length of text increases, the size of the label must adjust to accommodate the text. I called the sizeToFit method, and while the label adjusts its width correctly, the bottom of the text is cut off. The problem is that the text includes subscripts and superscripts , and the label is not adjusting itself with the subscripts in consideration (for example, with H₂O the bottom of the two is cut off).
Can I override sizeToFit or sizeThatFits: to increase the height of the label?
EDIT:
- (void) addCompound {
self.currentLabel = [[FormulaLabel alloc] initWithFrame:CGRectMake(10, 10, 100, 50)];
[self addSubview:self.currentLabel];
[self.currentLabel sizeToFit];
// Right now self.currentlabel.text = "". However, I've confirmed thru NSLogging that letters are added to self.currentLabel.text as the user types on the keyboard. Also, the text displays properly (as long as it's within the original frame) when I remove [sel.currentLabel sizeToFit]
}
You should override the UILabel method (CGSize)sizeThatFits:(CGSize)size in your subclass like example below. I just add 10pt to the height calculated by UILabel to accommodate the subscript.
#implementation ESKLabel
- (CGSize)sizeThatFits:(CGSize)size
{
CGSize theSize = [super sizeThatFits:size];
return CGSizeMake(theSize.width, theSize.height + 10);
}
#end
Sample output:
self.eskLabel.text = #"Hello Long² Long\u2082 World";
NSLog(#"CGSize: %#", NSStringFromCGSize(self.eskLabel.frame.size));
[self.eskLabel sizeToFit];
NSLog(#"CGSize: %#", NSStringFromCGSize(self.eskLabel.frame.size));
From the NSLog:
This GDB was configured as "x86_64-apple-darwin".sharedlibrary apply-load-rules all Attaching to process 864.
2012-01-06 23:34:21.949 Stackoverflow4[864:f803] CGSize: {85, 61}
2012-01-06 23:34:21.951 Stackoverflow4[864:f803] CGSize: {302, 44}
kill
quit
This should to the trick:
self.eskLabel.adjustsFontSizeToFitWidth = YES;