Say,
UILabel *label = [[UILabel alloc] init];
....
[view1 addSubview:label];
[view2 addSubview:label];
Doesn't addSubview retain a reference to the current label? That is, it has its own copy of the UILabel but why the label only shows up on view 2?
Thanks.
That does not work because view can have only 1 superview. So if you want to have multiple instances of same label in different views you have to create a copy of your label yourself and add it to another superview.
Quote from reference:
Views can have only one superview. If
view already has a superview and that
view is not the receiver, this method
removes the previous superview before
making the receiver its new superview.
Related
I have a UIView inside a UIScrollView. I can easily pin the height to the UIScrollView's frame height.
How do I add a constraint that pins to the UIScrollView's contentSize instead?
Thanks!
UIScrollView have dynamic constraints, left, top, width and height are generated at runtime. If you put a UIView inside a UIScrollview and Pin fixed constraints in Interface Builder it will generate an error because the parameters are relative to Superview/Container View.
You can try some workarounds:
1- Add UIView constraints Programmatically
http://www.thinkandbuild.it/learn-to-love-auto-layout-programmatically/
2- Manually resize your view bounds in initWithFrame function inside a UIView Subclass
Please give me any feedback about your progress.
The answer "Adding constraints programatically" is correct but it was a little light on detail for me to accept it as the full answer.
Here's how I did it!
Remove all storyboard constraints on the WebView
Add a Placeholder constraint in storyboard for the constraint that you will add with code. This step is very important (and easily missed) or you will get an error about conflicting constraints.
Add code to webviewDidFinishLoad delegate method
--Code--
- (void)webViewDidFinishLoad:(UIWebView *)webView {
_scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width, _headerImageView.frame.size.height + webView.scrollView.contentSize.height);
_webView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewsDictionary = #{#"myWebView":_webView};
NSString *constraintsString = [NSString stringWithFormat:#"V:[myWebView(%i)]", (int)_scrollView.contentSize.height];
NSArray *constraint_H = [NSLayoutConstraint constraintsWithVisualFormat:constraintsString options:0 metrics:nil views:viewsDictionary];
[_webView addConstraints:constraint_H];
}
The following steps for UIScrollView+autolayout has been working for me, but not in the iOS8/Xcode 6 preview: (using storyboard, size class enabled):
add a scrollview to the root view.
pin zero spaces to all edges of super view.
add a UIView (contentView) to the above scrollview.
pin zero spaces to all edges of the scrollview
add some widgets to contentView and change the height of the contentView to 2000.
=> this contentView scrolls in iOS 7, but I cannot get the same steps working in iOS 8 preview.
Even it seems working in iOS 7, it is possible that I may not doing the right way? Any suggestions?
I'm surprised not to have seen more comment about this. Scroll view internal autolayout is largely broken in iOS 8 (as seeded up to the time of this writing).
EDIT This was fixed in seed 5, so this note should be ignored!
The rule is supposed to be (see https://developer.apple.com/library/prerelease/ios/technotes/tn2154/_index.html) that if the content of a scroll view (its subview or subviews) is pinned to all four bounds of the scroll view, it sets the content size.
In iOS 8, however, this fails - but in an odd way. It fails only if the constraints determining the height and width of the subviews are all absolute as opposed to intrinsic.
Thus, for example, consider the code at the bottom of that tech note, where a scroll view and a really big image view are created in code (here it is; I have corrected a small typo where an at-sign was dropped):
- (void)viewDidLoad {
UIScrollView *scrollView;
UIImageView *imageView;
NSDictionary *viewsDictionary;
// Create the scroll view and the image view.
scrollView = [[UIScrollView alloc] init];
imageView = [[UIImageView alloc] init];
// Add an image to the image view.
[imageView setImage:[UIImage imageNamed:#"MyReallyBigImage"]];
// Add the scroll view to our view.
[self.view addSubview:scrollView];
// Add the image view to the scroll view.
[scrollView addSubview:imageView];
// Set the translatesAutoresizingMaskIntoConstraints to NO so that the views
// autoresizing mask is not translated into auto layout constraints.
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
imageView.translatesAutoresizingMaskIntoConstraints = NO;
// Set the constraints for the scroll view and the image view.
viewsDictionary = NSDictionaryOfVariableBindings(scrollView, imageView);
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|[scrollView]|"
options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[scrollView]|"
options:0 metrics: 0 views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|[imageView]|"
options:0 metrics: 0 views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[imageView]|"
options:0 metrics: 0 views:viewsDictionary]];
}
That code works (assuming you have a really big image), because the image view is sized by intrinsic constraints. But now change the last two lines, like this:
[scrollView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|[imageView(1000)]|"
options:0 metrics: 0 views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[imageView(1000)]|"
options:0 metrics: 0 views:viewsDictionary]];
Now what you have is a scroll view that is scrollable on iOS 7 but is NOT scrollable on iOS 8. Further investigation shows that this is because the content size remains at (0,0); it does not respect the absolute width and height constraints of the content view.
Use following step for UIScrollView + AutoLayout
Add scroll view to the root view
Add contain view to above scroll view
Add Following constraint for scroll view
Trailing space to super view = 0
Leading Space to super view =0
Top space to super view = 0
Bottom Space to super view = 0
Add Following Constraint for contain view of scroll view
(in this case scroll view is super view)
Trailing space to super view = 0
Leading Space to super view =0
Top space to super view = 0
Bottom Space to super view = 0
Height of contain view (if you are using vertical scrolling) otherwise width of contain view (if you are using Horizontal scrolling).
Horizontal canter alignment (if you are using vertical scrolling) otherwise vertical canter alignment (if you are using Horizontal scrolling).
I'm trying to create a NSTableView inside a NSScrollView (the standard configuration, that is) in code, using auto layout. I can't figure out how to make this work.
Here's my loadView:
- (void)loadView
{
NSView *view = [[NSView alloc] init];
NSScrollView *tableScroll = [[NSScrollView alloc] init];
NSTableView *fileTable = [[NSTableView alloc] init];
[tableScroll setDocumentView:fileTable];
[tableScroll setHasVerticalScroller:YES];
[tableScroll setHasHorizontalScroller:NO];
fileTable.delegate = self;
fileTable.dataSource = self;
[fileTable setHeaderView:nil];
[fileTable setAllowsColumnReordering:NO];
NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:#"column1"];
[fileTable addTableColumn:column];
[tableScroll setTranslatesAutoresizingMaskIntoConstraints:NO];
[fileTable setTranslatesAutoresizingMaskIntoConstraints:NO];
[view addSubview:tableScroll];
NSDictionary *topViews = NSDictionaryOfVariableBindings(tableScroll);
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[tableScroll]|" options:0 metrics:nil views:topViews]];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[tableScroll]|" options:0 metrics:nil views:topViews]];
self.fileTable = fileTable;
self.view = view;
}
What happens is that my table view's frame will be always equal to the bounds of the NSClipView. The view is inside a window and gets resized with it, and when I do that it'll resize the scrollview, the clip view and the table, but I can never scroll anywhere.
Looking at constraints I get, the NSScrollView gets constraints that set the clip view to fill it, the clip view has no constraints at all and the table view has a bunch of constraints related to the NSTableRowViews inside it.
If I add a constraint like |[fileTable(>=500)] to the clip view I'll get 500 pixels of NSTableView, but obviously I don't want to do that.
Even though this was answered by the poster in the comments above, I thought I’d put the answer here (having run into the same issue). If you are adopting auto layout, you would typically uncheck “Translates Mask Into Constraints” in the xib. However, for classes like NSScrollView and NSTableView, you should generally let them manage their own internal views by setting their translatesAutoresizingMaskIntoConstraints property to YES. It is still ok to set constraints that are external to these views, i.e. to resize in relation to their superview.
If you set translatesAutoresizingMaskIntoConstraints to NO, then you will need to supply constraints for all of the internal views, which unless you specifically need custom behavior (almost never), you will not want to do. This was the specific problem above.
An obvious side effect of not setting this correctly is that a table (for example) will not properly scroll beyond what is visible in the view.
I have a subview loaded into an UIView. In the subview's .m file I have the following:
- (void)startAnimation {
// Array to hold png images
imageArray = [[NSMutableArray alloc] initWithCapacity:22];
animatedImages = [[UIImageView alloc] initWithImage:viewForImage];
// Build array of images, cycling through image names
for (int i = 1; i < 22; i++){
[imageArray addObject:[UIImage imageNamed:[NSString stringWithFormat:#"image%d.png", i]]];
}
animatedImages.animationImages = [NSArray arrayWithArray:imageArray];
// One cycle through all the images takes 1 seconds
animatedImages.animationDuration = 2.0;
// Repeat forever
animatedImages.animationRepeatCount = 0;
// Add subview and make window visible
[viewForMovie addSubview:animatedImages];
// Start it up
animatedImages.startAnimating;
NSLog(#"Executed");
}
Please be noted that I have in the .h file:
UIImageView *animatedImages;
NSMutableArray *imageArray;
UIView *viewForMovie;
#property(nonatomic,retain)IBOutlet UIView *viewForMovie;
and in the .m file:
#synthesize viewForMovie;
and I have connected viewForMovie to a UIView in IB. I've been on this for several hours now and have tried many variations I've found on the web but cannot get it to work. There are no errors and the other GUI graphics in the subview appear very nicely....but the animation just doesn't appear over top where it should. Also the NSlog reports that the method has in fact been called from the parent. Can anyone see any blaring issues? Thx.
PS: I'm pretty new at this.
Based on the code shown and the behavior you see so far, here are my suggestions:
Make sure the viewForMovie IBOutlet is connected properly in Interface Builder. If it's not connected properly (and so nil), nothing will appear. If you didn't mean to make it an IBOutlet in the first place, then you'll need to manually create it and add it as a subview to self before using it.
Not sure why you have the viewForMovie UIView in the first place. Is this subview's class (let's call it MySubview) a subclass of UIView? You can just show the animation in self instead of adding another subview inside it. Are you going to add more uiviews to this subview besides the viewForMovie?
To get rid of the "may not respond to" warning, declare the startAnimation method in the MySubview.h file (under the #property line):
-(void)startAnimation;
The fact that the warning says "UIView may not respond" also tells you that the parent view has declared newView as a UIView instead of MySubview (or whatever you've named the subview class). Change the declaration in the parent from UIView *newView; to MySubview *newView;.
In the initWithImage, what is "viewForImage"? Is it a UIImage variable or something else?
If all of the images are the same size and fit in the subview as-is, you don't need to set the frame--the initWithImage will automatically size the UIImageView using the init-image dimensions.
Double check that the images you are referencing in the for-loop are named exactly as they are in the code and that they have actually been added to the project.
Finally, you should release the objects you alloc in startAnimation. At the end of the method, add:
[imageArray release];
[animatedImages release];
The only item, however, that I think is actually preventing the animation from appearing right now is item 1.
I am doing manual layouting for my Cocoa application and at some point I need to figure out what the inner size of a NSView subclass is. (E.g. What is the height available for my child view inside of a NSBox?)
One of the reasons is that I am using a coordinate system with origin at the top-left and need to perform coordinate transformations.
I could not figure out a way to get this size so far and would be glad if somebody can give me a hint.
Another very interesting property I would like to know is the minimum size of a view.
-bounds is the one you're looking for in most views. NSBox is a bit of a special case, however, since you want to look at the bounds of the box's content view, not the bounds of the box view itself (the box view includes the title, edges, etc.). Also, the bounds rect is always the real size of the box, while the frame rect can be modified relative to the bounds to apply transformations to the view's contents (such as squashing a 200x200 image into a 200x100 frame).
So, for most views you just use [parentView bounds], and for NSBox you'll use [[theBox contentView] bounds], and you'll use [[theBox contentView] addSubview: myView] rather than [parentView addSubview: myView] to add your content.
Unfortunately, there is no standard way to do this for all NSView subclasses. In your specific example, the position and size of a child view within an NSBox can be computed as follows:
NSRect availableRect = [someNSBox bounds];
NSSize boxMargins = [someBox contentViewMargins];
availableRect = NSInsetRect(availableRect, boxMargins.width, boxMargins.height);
If you find yourself using this often, you could create a category on NSBox as follows:
// MyNSBoxCategories.h
#interface NSBox (MyCategories)
- (NSRect)contentFrame;
#end
// MyNSBoxCategories.m
#implementation NSBox (MyCategories)
- (NSRect)contentFrame
{
NSRect frameRect = [self bounds];
NSSize margins = [self contentViewMargins];
return NSInsetRect(frameRect, margins.width, margins.height);
}
#end
And you would use it like so:
#import "MyNSBoxCategories.h"
//...
NSRect frameRect = [someNSBox contentFrame];
[myContentView setFrame:frameRect];
[someNSBox addSubview:myContentView];
The bounds property of NSView returns an NSRect with the origin (usually (0,0)) and the size of an NSView. See this Apple Developer documentation page.
I'm not sure (I never had to go too deep in that stuff), but isn't it [NSView bounds]?
http://www.cocoadev.com/index.pl?DifferenceBetweenFrameAndBounds