UIScrollView + Autolayout issue - uiscrollview

Considering the following structure:
| UIScrollView | UITextField | (UIScrollView on the left and a UITextField the right)
I am trying to add a UILabel into the UIScrollView and make the UIScrollView grow according to the UILabel size. The UITextFiled would then get smaller according to the grow of the UIScrollView.
- (IBAction)makeScrollViewGrow:(id)sender
self.myScrollView.translatesAutoresizingMaskIntoConstraints = NO;
self.myTextField.translatesAutoresizingMaskIntoConstraints = NO;
UILabel *myLabel = [[UILabel alloc] init];
myLabel.text = #"THIS IS A LONG MESSAGE";
myLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.myScrollView addSubview:myLabel];
UITextField *theTextField = self.myTextField;
[self.myTextField removeConstraint:self.titleTextFieldWidthConstraint];
[self.myScrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[myLabel]|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(myLabel)]];
[self.myScrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[myLabel]|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(myLabel)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-20-[theSCrollView]-8-[theTextField]-20-|" options:0 metrics: 0 views:NSDictionaryOfVariableBindings(theSCrollView, theTextField)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[theSCrollView]|" options:0 metrics: 0 views:NSDictionaryOfVariableBindings(theSCrollView)]];
}
The issue here is that the UIScrollView is not being resized because the UITextField has a fixed width. If I remove programatically the width UITextField constraint the UIScrollView takes over the all width without considering the text Label (intrinsicContentSize).
Will I need to set the Scrollview contentsize manually or can I do this automatically using autolayout?
Thanks

Related

Auto Layout on OS X - make NSTextField fill superview

I can't get Auto Layout to work on OS X. What I'm trying to do is pretty simple, namely I have an NSTextField that is supposed to fill the entire space of its superview. Here's the minimal working example code I'm using:
#import AppKit;
int main() {
#autoreleasepool {
NSWindow *window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 300, 300)
styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask)
backing:NSBackingStoreBuffered
defer:NO];
NSTextField *textField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)];
textField.stringValue = #"Lorem ipsum dolor sit atmet.";
[window.contentView addSubview:textField];
textField.translatesAutoresizingMaskIntoConstraints = NO;
window.contentView.translatesAutoresizingMaskIntoConstraints = NO;
[textField setContentHuggingPriority:1
forOrientation:NSLayoutConstraintOrientationHorizontal];
[textField setContentHuggingPriority:1
forOrientation:NSLayoutConstraintOrientationVertical];
[window.contentView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[textField]-|"
options:0
metrics:nil
views:#{#"textField": textField}]];
[window.contentView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[textField]-|"
options:0
metrics:nil
views:#{#"textField": textField}]];
window.contentView.wantsLayer = YES;
window.contentView.layer.borderWidth = 5;
[window makeKeyAndOrderFront:nil];
[[NSApplication sharedApplication] run];
[textField release];
[window release];
}
return EXIT_SUCCESS;
}
When I run this, the text field doesn't appear. When you resize the window, you can clearly see the border of the content view resizing properly. What am I doing wrong?
I believe you shouldn't alter NSWindow's contentView behavior, e.g. setting its translatesAutoresizingMaskIntoConstraints property.
Removing the following line works for me:
window.contentView.translatesAutoresizingMaskIntoConstraints = NO;

uiscrollview autolayout refresh after move scroll

I have IBOutlet 2 property, scrollview and contentview, i create my view via xib, I Set all consraint inside contentview, now I need to put contentview inside scrollview, I try to do something like this:
[_scrollView addSubview:_scrollViewContent];
[_scrollView setContentSize:_scrollViewContent.frame.size];
_scrollView.translatesAutoresizingMaskIntoConstraints = NO;
_scrollViewContent.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *views = NSDictionaryOfVariableBindings(_scrollView, _scrollViewContent);
NSArray *scrollViewLabelConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_scrollViewContent(_scrollView)]" options:0 metrics:nil views:views];
[_scrollView addConstraints:scrollViewLabelConstraints];
initially not work, contentview is a bit larger then scrollview, when I scroll horizontally I see the view in the correct way.
Where is the error?

Programmatically implementing two different layouts using size classes

I have a four buttons layout. In portrait they should be shown one above the other. In landscape they should be in two columns each with two buttons.
I implement the buttons in code - really simple stuff:
UIButton *btn1 = [[UIButton alloc] init];
[self.view addSubview: btn1];
UIButton *btn2 = [[UIButton alloc] init];
[self.view addSubview: btn2];
UIButton *btn3 = [[UIButton alloc] init];
[self.view addSubview: btn3];
UIButton *btn4 = [[UIButton alloc] init];
[self.view addSubview: btn4];
NSDictionary *views = NSDictionaryOfVariableBindings(btn1, btn2, btn3, btn4);
[btn1 setTranslatesAutoresizingMaskIntoConstraints:NO];
[btn2 setTranslatesAutoresizingMaskIntoConstraints:NO];
[btn3 setTranslatesAutoresizingMaskIntoConstraints:NO];
[btn4 setTranslatesAutoresizingMaskIntoConstraints:NO];
// portrait constraints
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(50)-[btn1]-(50)-|"
options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(50)-[btn2]-(50)-|"
options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(50)-[btn3]-(50)-|"
options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(50)-[btn4]-(50)-|"
options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[btn1]-[btn2]-[btn3]-[btn4]-(50)-|"
options:0 metrics:nil views:views]];
This is obviously the setup for portrait layout. I would used to have determined the device and its orientation to make specific case for iPad and iPhone in there respective orientations. But now we are supposed to use size classes. How can I determine if the size class is "compact"... and thus set the appropriate constraints?
In the meantime I have found a good solution. Since this question has so many upvotes, I thought I would quickly describe it. I was inspired to this solution by a WWDC session.
I have moved on to Swift so please excuse that the code will be in swift - the concept is however the same for Obj-C.
You start out by declaring three constraint arrays:
// Constraints
private var compactConstraints: [NSLayoutConstraint] = []
private var regularConstraints: [NSLayoutConstraint] = []
private var sharedConstraints: [NSLayoutConstraint] = []
And then you fill the constraints accordingly. You can i.e. do this in a separate function that you call from viewDidLoad or you do it in viewDidLoad directly.
sharedConstraints.append(contentsOf: [
btnStackView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
...
])
compactConstraints.append(contentsOf: [
btnStackView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.7),
...
])
regularConstraints.append(contentsOf: [
btnStackView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.4),
...
])
The important part is switching between the size classes and activating/deactivating the appropriate constraints.
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if (!sharedConstraints[0].isActive) {
// activating shared constraints
NSLayoutConstraint.activate(sharedConstraints)
}
if traitCollection.horizontalSizeClass == .compact && traitCollection.verticalSizeClass == .regular {
if regularConstraints.count > 0 && regularConstraints[0].isActive {
NSLayoutConstraint.deactivate(regularConstraints)
}
// activating compact constraints
NSLayoutConstraint.activate(compactConstraints)
} else {
if compactConstraints.count > 0 && compactConstraints[0].isActive {
NSLayoutConstraint.deactivate(compactConstraints)
}
// activating regular constraints
NSLayoutConstraint.activate(regularConstraints)
}
}
I know that the constraints don't fit to the ones in the question. But the constraints themselves are irrelevant. The main thing is how one switches between two sets of constraints based on the size class.
Hope this helps.
You can examine the view's trait collection to determine its horizontal and vertical size class.
if (self.view.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) {
...
}
Implement the traitCollectionDidChange: method to automatically be called when a trait changes due to autorotation.
For more information, see UITraitCollection Class Reference and UITraitEnvironment Protocol Reference.
Swift 4 code for the accepted answer:
if (self.view.traitCollection.horizontalSizeClass == .compact) {
...
}
traitCollection only works in iOS8. So your app will crash on iOS7. Use the code below to support both iOS7 and iOS8
if ([self.view respondsToSelector:#selector(traitCollection)]){
if (self.view.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) {
...
}
}

UIScrollView Autolayout prevent from scrolling vertically

I'm wondering how to crop image inside UIscrollView with autolayout
I'm trying to make UIscrollView scroll only horizontally. if image is higher than view height it should be cropped. I've tried a lot properties but can't make all images inside uiscrollview same height as view to avoid scrolling vertically.
Do i miss something?
#import "WelcomeController.h"
#interface WelcomeController ()
#property (strong, nonatomic) UIScrollView *scrollView;
#property (nonatomic,strong) NSArray *contentList;
#end
#implementation WelcomeController
#synthesize contentList =_contentList;
- (void)updateUI
{
UIScrollView* sv = self.scrollView;
id previousLab = nil;
for (UIView *lab in _contentList) {
lab.translatesAutoresizingMaskIntoConstraints = NO;
lab.backgroundColor = [UIColor blackColor];
lab.contentMode = UIViewContentModeScaleAspectFit;
[sv addSubview:lab];
[sv addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[lab]|"
options:0 metrics:nil
views:#{#"lab":lab}]];
if (!previousLab) { // first one, pin to top
[sv addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[lab]"
options:0 metrics:nil
views:#{#"lab":lab}]];
} else { // all others, pin to previous
[sv addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"H:[prev][lab]"
options:0 metrics:nil
views:#{#"lab":lab, #"prev":previousLab}]];
}
previousLab = lab;
}
// last one, pin to bottom and right, this dictates content size height
[sv addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[lab]|"
options:0 metrics:nil
views:#{#"lab":previousLab}]];
[sv addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:[lab]|"
options:0 metrics:nil
views:#{#"lab":previousLab}]];
}
-(void)setContentList:(NSArray *)contentList
{
_contentList = contentList;
[self updateUI];
}
- (void)setupScrollView
{
UIScrollView* sv = [UIScrollView new];
sv.backgroundColor = [UIColor redColor];
sv.translatesAutoresizingMaskIntoConstraints = NO;
sv.pagingEnabled = YES;
sv.showsHorizontalScrollIndicator =NO;
sv.showsVerticalScrollIndicator = NO;
sv.bounces =NO;
[self.view addSubview:sv];
[self.view addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[sv]|"
options:0 metrics:nil
views:#{#"sv":sv}]];
[self.view addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[sv]|"
options:0 metrics:nil
views:#{#"sv":sv}]];
self.scrollView = sv;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupScrollView];
//for testing
UIImageView *image1=[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"welcome1.jpg"]];
UIImageView *image2=[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"welcome2.jpg"]];
UIImageView *image3=[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"welcome3.jpg"]];
self.contentList = [NSArray arrayWithObjects:image1,image2,image3,nil];
}
#end
Have you tried setting the height of the scroll view explicitly?
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[_sv(123)]" options:0 metrics:nil views:views]];
You would need to replace the height above (123) with the height you need, obviously, as well as views.
Autolayout automatically manages the UIScrollView's contentSize (the scrollable area). So if you stick a subview in there with an intrinsic size that is larger than the height, it will increase the contentSize. I can think of two things:
Stick images in a plain UIView with the same height as the scrollview.
Subclass the UIImageView and override the intrinsicContentSize method to return a fixed height for all images. This seems like a poor solution, though.
I think you should be able to set the image view's frames to a fixed height (via constraints). Then add constraints to have the image views top and bottom fixed with x constant from the scrollview.
This will let the scrollview know its exact content size to use. As long as its frame, then, (determined by whatever constraints you give it in relation to its superview) is >= the image view's fixed heights, it won't scroll vertically.

How does NSScrollView works?

How does NSScrollView works in mac applications? I've write the following code but the scrolling is not working.
NSDictionary *temp=[[ NSDictionary alloc] init ];
NSScrollView *scroll = [[NSScrollView alloc ] initWithFrame:CGRectMake(0, 0, 100, 100)];
NSArray *dicArray=[NSArray arrayWithObjects:dict,dict1,dict2,dict3,dict4, nil];
for (int i=0; i<[dicArray count]; i++)
{
int offset=100;
int x=10;
int y=20;
y=y+(i*offset);
temp= [dicArray objectAtIndex:i];
NSRect titleRect=NSMakeRect(x,y,100,30);
NSRect subtitleRect=NSMakeRect(x, y+20, 400, 20);
NSTextField *title=[[NSTextField alloc] initWithFrame:titleRect];
[[title cell] setStringValue:[temp objectForKey:key]];
[[title cell] setWraps:NO];
[[title cell] setScrollable:YES];
[[title cell] setEditable:NO];
[[title cell] setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSMinusSetExpressionType]]];
[title setDrawsBackground:NO];
[title setBordered:NO];
NSTextField *subtitle=[[NSTextField alloc] initWithFrame:subtitleRect ];
[[subtitle cell] setStringValue:[temp objectForKey:subkey]];
[[subtitle cell] setWraps:NO];
[[subtitle cell] setScrollable:YES];
[[subtitle cell] setEditable:NO];
[[subtitle cell] setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSMiniControlSize]]];
[subtitle setDrawsBackground:NO];
[subtitle setBordered:NO];
[scroll addSubview:title];
[scroll addSubview:categoryOkButton];
[scroll addSubview:subtitle];
}
[[self window] setContentView:scroll];
[scroll release];
My Cocoa is a bit rusty, but NSScrollView works in a different way than UIScrollView. You don't just add subviews, that doesn't work with NSScrollView.
What you need is a container view into which you add all your subviews. So you create an NSView of the appropriate size and add all your views to that container view. Then you set:
[myScrollView setDocumentView:myContainerView];
See the Scroll View Programming Guide for more details.

Resources