I am trying to open a new view on select of a table cell in a previous view. The new view that I am trying to open, consists of different sub-views or modules. Hence, I populate each sub-view one by one inside in [self populate] method which is in triggered inside the viewDidLoad method.
-(void)viewDidLoad{
[super viewDidLoad];
[self populate];
}
-(void) populate{
[self.edgeGallery loadImagesWithURLs: _items];
// Modular view: main info
[self.vwListingMainView setListing: _listing];
[self.vwListingMainView refresh];
// Modular view: listing agents
_vwListingAgentsView.agentsArray = _listing.agents;
// Modular view: listing info
_vwListingInfoView.listing = _listing;
[_vwListingInfoView refresh];
// Modular view: Listing activities
_vwListingActivityView.listing = _listing;
[_vwListingActivityView requestCounts];
}
Every time a new subview is populated, the method viewWillLayoutSubViews is called. This is the method where I compute the subview's height and other constraints and append it to the superview.
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[self computeAndFixHeight];
}
- (void) computeAndFixHeight {
// Adjusting each module's height
_cstMainInfoHeight.constant = [_vwListingMainView getViewHeight];
_cstListingActionsHeight.constant = [_vwListingActionsView getViewHeight];
_cstListingAgentsHeight.constant = [_vwListingAgentsView getViewHeight];
_cstListingInfoViewHeight.constant = [_vwListingInfoView getViewHeight];
// Adjusting scroll view height
NSInteger computedScrollHeight = _vwListingUpcomingEventView.frame.origin.y + [_vwListingUpcomingEventView getViewHeight];
[self.scrollView setContentSize:CGSizeMake(self.view.frame.size.width, computedScrollHeight)];
_cstContainerBottom.constant = -computedScrollHeight - kDefaultNegativeScrollH;
[self.view layoutIfNeeded];
[self.view updateConstraints];
}
However, once the view is loaded completely, the problem that I am facing is that sometimes, I get the complete view and sometimes, randomly, I get an empty view. I think [self.view layoutIfNeeded] is the problem, but I have also tried using [self.view setNeedsLayout] and [self.view setNeedsUpdateConstraints], but still the problem remains. Any help would be appreciated.
Please excuse me if I am doing anything stupid. I am new to iOS development.
I created this project just for your question to show how to update a scrollView just using AutoLayout (no need to override viewWillLayoutSubviews, update the scrollView's contentSize, call layoutIfNeeded or updateConstraints)
Hope it helps you =)
https://github.com/ghashi/ScrollViewQuestion/tree/master
This is the result:
In our project we had encountered similar issue where we had to add a lot of views to a content view of a scrollview using constraints added programatically. Writing constraints for each view not only made the view controller bloated, it was also static. To add another view we had to write the constraints again.
We end up creating subclass of UIView that now managed this for us. We named this NNVerticalStackView.h,.m.
How can I implement row-by-row scrolling in an NSTextView object? It has to work regardless of how the user tries to scroll: either if they drag the scroll knob or use a two-finger scroll gesture on the trackpad. Furthermore, the scroll knob should always move in discrete steps. (This is exactly the way Apples terminal emulator works.)
What I tried to do first was to subclass NSTextView and override adjustScroll: (which it inherits from NSView), which is described here, that is:
- (NSRect)adjustScroll:(NSRect)newVisible {
NSRect modifiedVisible = newVisible;
NSFont *font = [self font];
CGFloat lineHeight = [[self layoutManager] defaultLineHeightForFont:font];
modifiedVisible.origin.x = (int)(modifiedVisible.origin.x/lineHeight) * lineHeight;
modifiedVisible.origin.y = (int)(modifiedVisible.origin.y/lineHeight) * lineHeight;
return modifiedVisible;
}
This however only gives row-by-row scrolling when I use the scroll knob. By accident I found out that subclassing NSScrollView would give me row-by-row scrolling also when scrolling with the trackpad, even though it seems I'm not really doing anything (so why this works is a mystery), the only overridden methods are these:
- (instancetype)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
}
The scroll knob, however, still moves continuously. Do I have to subclass NSScroller and override setDoubleValue (I tried doing that, but it didn't work very well so that solution feels difficult, though maybe possible) or can I override reflectScrolledClipView: in my subclass to NSScrollView (that didn't work at all), or are there better solutions?
I'm using iCarousel to display editable question cards. The cards contain a UITextView for entering the question (or already contain text as you swipe through filled cards). However, when the carousel is presented and scrolled, sometimes text views appear empty.
This is due to a UITextView optimization of not drawing text offscreen. But text views in a UITableView will not suffer from this.
As many know, using setNeedsDisplay will NOT work due to the optimization, so it doesn't redraw the text.
I currently change the text view's frame by adding and then removing 1px. This forces a redraw. However, I can only do this when the item changes. iCarousel does not have a willDisplayCell delegate method. (Nick, can you add one easily? The code baffles me)
Because iCarousel is preloading many views for smoothness (which is necessary, setting iCarouselOptionVisibleItems doesn't fix anything) there doesn't seem to be anything else I can do but know when the view is about to come on screen. Suggestions?
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view
{
MIQuestionView *questionView = (MIQuestionView *)view;
if (questionView == nil)
{
MIQuestionType type = [self.testSection.questionType integerValue];
questionView = [[MIQuestionView alloc] initWithFrame:CGRectNull questionType:type];
questionView.delegate = self;
}
questionView.question = [self.testSection.questions objectAtIndex:index];
return questionView;
}
The text view is embedded in the MIQuestionView. The text is set in the question setter. There's no way for me to know when it's coming onto the screen. To be clear, I don't want to resize. The text is not drawn offscreen and appear blank when coming on-screen.
Sorry, I didn't look for ambiguous code above:
- (id)initWithFrame:(CGRect)frame questionType:(MIQuestionType)type
{
if (CGRectEqualToRect(frame, CGRectNull))
frame = CGRectMake(0, 0, 650, 244);
self = [super initWithFrame:frame];
...
It's an odd bug. I'm not sure if it's an issue with iCarousel or just a quirk of iOS that you need to deal with when dynamically adding and removing UITextViews from the view hierarchy.
I have a solution that's maybe a bit cleaner than the ones you've found though; Just add this to your MICardView:
- (void)didMoveToSuperview
{
if (self.superview)
{
_textView.frame = self.bounds;
}
}
This basically forces the textView to re-layout every time the cardView is recycled, and it avoids you having to do anything special in your view controller to work around the issue.
I am trying to implement a simple ImageView in a scrollview to display an image. I want to particularly implement the pinch to zoom feature. I implemented the following code (which is taken from one of the Objective C sample codes on the apple websites). I tested the zooming thoroughly on ios 5 and there seems to be no problem. However when I ported my code to ios 6 and I test my zoom feature, after a while I get the following error message and my app crashes with skewed zoom results.
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
NSAutoresizingMaskLayoutConstraint:0x8808360 h=--- v=--- V:[UIWindow:0x744db00(480)],
NSLayoutConstraint:0x71286d0 UIView:0x71222c0.bottom == UIWindow:0x744db00.bottom,
NSLayoutConstraint:0x7128650 V:|-(20)-[UIView:0x71222c0] (Names: '|':UIWindow:0x744db00 ),
NSLayoutConstraint:0x7120c50 UIImageView:0x7451630.bottom == UIScrollView:0x71206f0.bottom,
NSLayoutConstraint:0x7120c90 V:|-(0)-[UIImageView:0x7451630] (Names: '|':UIScrollView:0x71206f0 ),
NSLayoutConstraint:0x7120bd0 UIImageView:0x7451630.centerY == UIScrollView:0x71206f0.centerY,
NSLayoutConstraint:0x7122a50 V:|-(10)-[UIScrollView:0x71206f0] (Names: '|':UIView:0x71222c0 ),
NSLayoutConstraint:0x7122940 V:[UIScrollView:0x71206f0]-(38)-| (Names: '|':UIView:0x71222c0 )
Will attempt to recover by breaking constraint
Break on objc_exception_throw to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in may also be helpful.
I tried doing a google search for this problem but I am not getting good results. The code I am using is as follows. Help is greatly Appreciated
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// set the tag for the image view
[self.imageView setTag:ZOOM_VIEW_TAG];
// calculate minimum scale to perfectly fit image width, and begin at that scale
NSLog(#"[imageScrollView frame].size.width : %f",[self.scrollVIew frame].size.width);
NSLog(#"[imageView frame].size.width : %f",[self.imageView frame].size.width);
NSLog(#"[imageScrollView frame].size.height : %f",[self.scrollVIew frame].size.height);
NSLog(#"[imageView frame].size.height : %f",[self.imageView frame].size.height);
float minimumScale = [self.scrollVIew frame].size.width / [self.imageView frame].size.width;
minimumScale = 300.0/1200.0;
NSLog(#"minimumScale : %f",minimumScale);
[self.scrollVIew setMinimumZoomScale:minimumScale];
[self.scrollVIew setZoomScale:minimumScale];
NSLog(#"imageScrollView.zoomScale : %f",self.scrollVIew.zoomScale);
NSLog(#"imageScrollView.minimumZoomScale = minimumScale : %f",self.scrollVIew.minimumZoomScale = minimumScale);
self.scrollVIew.contentSize = CGSizeMake(1200.0, 800.0);
self.scrollVIew.delegate = self;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return [self.scrollVIew viewWithTag:ZOOM_VIEW_TAG];
}
Vivek
You need to create your UIImageView Programatically, and add in a subview of your UIScrollView. But you must make this inside of the viewDidAppear.
Just your UIScrollView can be made in StoryBoard!
And you will set the scrollView.contentSize inside of "viewDidAppear:(BOOL)animated" and not inside of the viewDidLoad.
Disable auto layout in your XIB file. It will be under the left most tab in the properties section on the right. You are probably using it without knowing it. If you are using it intentionally, then try to sort out your constraints so they don't conflict with each other.
I put a UIScrollView in my nib's view, and linked it to a an IBOutlet property.
Now, when I do this in my viewDidLoad method, it seems to have no effect on the contentSize:
self.sv.backgroundColor = [UIColor yellowColor]; // this works
CGSize size = CGSizeMake(1000.0, 1000.0);
[self.sv setContentSize:size]; // this does not
It behaves as if the contentSize was the same as the frame. What's going on?
This started working when I turned off AutoLayout. Why?
I had the same problem. Auto Layout for UIScrollView is messed up.
Work around: Put everything in the UIScrollView into another UIView, and put that UIView as the only child of the UIScrollView. Then you can use Auto Layout.
If things near the end is messed up (the end of whichever direction your UIScrollView scrolls), change the constraint at the end to have the lowest possible priority.
I tried viewWillLayoutSubviews to update scrollView's contentSize, it worked for me.
- (void)viewDidLayoutSubviews
{
[self.bgScrollView setContentSize:CGSizeMake(320, self.view.frame.size.height* 1.5)];
}
Apple Doc
-(void)viewDidLayoutSubviews
Called to notify the view controller that its view has just laid out its subviews.
Discussion
When the bounds change for a view controller’s view, the view adjusts the positions of its subviews and then the system calls this method. However, this method being called does not indicate that the individual layouts of the view’s subviews have been adjusted. Each subview is responsible for adjusting its own layout.
Your view controller can override this method to make changes after the view lays out its subviews. The default implementation of this method does nothing.
The easiest/cleanest way is to set contentSize at viewDidAppear so you negate the effects of autolayout. This doesn't involve adding random views. However relying on load order for an implementation to work may not be the best idea.
Use this code. ScrollView setContentSize should be called async in main thread.
Swift:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
DispatchQueue.main.async {
var contentRect = CGRect.zero
for view in self.scrollView.subviews {
contentRect = contentRect.union(view.frame)
}
self.scrollView.contentSize = contentRect.size
}
}
Objective C:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
dispatch_async(dispatch_get_main_queue(), ^ {
CGRect contentRect = CGRectZero;
for(UIView *view in scrollView.subviews)
contentRect = CGRectUnion(contentRect,view.frame);
scrollView.contentSize = contentRect.size;
});
}
There are two problems here. (1) viewDidLoad is too soon; you have to wait until after layout has taken place. (2) If you want to use autolayout with a scrollview that comes from a nib, then either you must use constraints to completely describe the size of the contentSize (and then you don't set the contentSize in code at all), or, if you want to set it in code, you must prevent the constraints on the scrollview's subviews from dictating the contentSize. It sounds like you would like to do the latter. To do so, you need a UIView that acts as the sole top-level subview of the scrollview, and in code you must set it to not use autolayout, enabling its autoresizingMask and removing its other external constraints. I show an example of how to do that, here:
https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/ch20p573scrollViewAutoLayout/ch20p573scrollViewAutoLayout/ViewController.m
But notice also the next example, which shows how to use constraints completely, instead of contentSize.
A SUPER easy way to use AutoLayout with UIScrollViews inside Interface Builder:
Step 1: Create a UIScrollView
Step 2: Create a UIView that is a child of your scroll view like so:
-UIScrollView
---UIView
-----Your other content
(We'll call this one contentView).
Step 3: In the size inspector, give this view a height and width (say, 320x700).
Step 4 (using AutoLayout): Create unambiguous constraints from your contentView to its superview (the UIScrollView): connect the 4 edges (top, leading, trailing, bottom), then give it a defined width and height that you want it to scroll too.
For example: If your scroll view spans the entire screen, you could give your content view a width of [device width] and a height of 600; it will then set the content size of the UIScrollView to match.
OR:
Step 4 (not using AutoLayout): Connect both of these new controls to your view controller using IB (ctrl+drag from each control to your view controller's .h #implementation). Let's assume each is called scrollView and contentView, respectively. It should look like this:
#interface YourViewController : UIViewController
#property (strong, nonatomic) IBOutlet UIScrollView *scrollView;
#property (strong, nonatomic) IBOutlet UIView *contentView;
#end
Step 5 (not using AutoLayout): In the view controller's .h file add (actually, override) the following method:
-(void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.scrollView.contentSize = self.contentView.frame.size;
}
You can use this lines of code into your *.m file's
- (void)viewDidLoad{
[scroll setContentSize:CGSizeMake(320, 800)] ;
[scroll setScrollEnabled:TRUE];
[scroll setShowsVerticalScrollIndicator:NO];
[scroll setShowsHorizontalScrollIndicator:YES];
}
for this you need to take an IBOutlet property of UIScrollView into your *.h file this way:
IBOutlet UIScrollView *scroll;
And connect this from Storyboard.
Or,
You can use this method into your *.m file:
-(void)viewDidLayoutSubviews
{
[scroll setContentSize:CGSizeMake(320, self.view.frame.size.height* 1.5)];
// this will pick height automatically from device's height and multiply it with 1.5
}
This both solution works for me in xcode-5, xcode-6, xcode-6.1, xcode-6.2
Setting the contentSize in viewDidAppear is critical.
But I also had a variation of what worked in the 3.5 inch screen, and the 4 inch screen. The 4 inch screen worked, the older one does not. Both iOS 7. Bizarre is an understatement!
I could never get auto layout based on constraints to work. Since my view was already a subclass UIScrollView I solved it by overriding setContentView: and ignoring auto layouts zero height setContentSize: message.
#interface MyView : UIScrollView {}
#end
#implementation MyView
- (void)setContentSize:(CGSize)aSize {
if (aSize.height > 0)
[super setContentSize:aSize];
}
#end
I used to do set up the uiscrollview programmatically UNTIL I watched the following wonderful tutorial, step by step how to get uiscrollview and uiview to work: https://www.youtube.com/watch?v=PgeNPRBrB18
After watching the video you will start liking Interface Builder I am sure.
Vote up
Still not scrolling when dynamic height of labels exceeds view height.
I did what yuf's answer marked as correct above said to do (I added a content view to my scrollview and set the constraints leading, trailing, top bottom, and equal widths from the content view to the scroll view.) but still my view was not scrolling when the internal controls height exceeded the height of the scrollview.
Inside my content view I have an image and 3 labels below it. Each label adjusts their own height dependant on how much text is in them (they are set to word-wrap and numberoflines = 0 to achieve this).
The problem I had was my content view's height was not adjusting with the dynamic height of the labels when they exceeded the height of the scroll view/main view.
To fix this I worded out I needed to set the Bottom Space to Container constraint between my bottom label and the contentview and gave it a value of 40 (chosen arbitrarily to give it a nice margin at the bottom). This now means that my contentview adjusts its height so that there is a space between the bottom of the last label and itself and it scrolls perfectly!
Yay!
Try this out...
add all constraints like you do for UIView (See screenShot of my ViewControler in Storyboard)
Now trick begins. select your last object and select its bottom constraint. (See above screenShot, Instagram button's Bottom Constraint(Yellow line)) and Change the Constant in Size Inspector like in bellow screenshot.
i require Constant=8 but you can change as per your requirements.
this Constant is the Space between That Orange Button's Bottom and the scrollView.
EDIT
Make Sure about your view's hierarchy .
0) ViewController.view (optional)
1) UIScrollView
2) UIView (Rename as "contentView")
3) UIView (this view is your content that will make scrollView scroll)
I finally worked out my own solution to this problem because in my case I couldn't use the view controller's life cycle. Create your own scroll view subclass and use it instead of UIScrollView. This even worked for a scroll view inside a collection view cell.
class MyScrollView:UIScrollView {
var myContentSize:CGSize = CGSize.zero // you must set this yourself
override func layoutSubviews() {
super.layoutSubviews()
contentSize = myContentSize
}
}
My MyScrollView was defined in the nib with a tag of 90. If so this is a good way to set content size in the code in the parent view.
let scrollView = viewWithTag(90) as! MyScrollView
scrollView.myContentSize = ...
If you are using AutoLayout a really easy way to set the contentSize of a UIScrollView is just to add something like this:
CGFloat contentWidth = YOUR_CONTENT_WIDTH;
NSLayoutConstraint *constraintWidth =
[NSLayoutConstraint constraintWithItem:self.scrollView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeLeading
multiplier:1
constant:contentWidth];
[self.scrollView addConstraint:constraintWidth];
I got Autolayout to work for paginated scroll views whose pages occupy the full-width of the screen. The pages automatically resize according to the scroll view's size. I haven't tested this for lesser-width scroll views but do comment away if it works--I beleieve it should. Targeted for iOS 9, wrote code in Swift 2, used a mix of IB's and custom code in awakeFromNib.
Steps:
Define a full-screen scroll view.
Inside the scroll view, add a UIView (I called mine contentView) whose top, trailing, bottom, and leading edges to the scroll view are all zero; the height is equal to the scroll view's; but the width is the scroll view's width times the number of pages. If you're doing this visually, you will see your content view extend beyond your scroll view in Inteface Builder.
For every "page" inside the contentView, add Autolayout rules to put them side-by-side each other, but most importantly, give them each a constraint so that their widths are equal to the scroll view's, not the content view's.
Sample code below. embedChildViewController is just my convenience method for adding child VCs--do look at setupLayoutRulesForPages. I have exactly two pages so the function is too simple, but you can expand it to your needs.
In my view controller:
override func loadView() {
self.view = self.customView
}
override func viewDidLoad() {
super.viewDidLoad()
self.embedChildViewController(self.addExpenseVC, toView: self.customView.contentView, fillSuperview: false)
self.embedChildViewController(self.addCategoryVC, toView: self.customView.contentView, fillSuperview: false)
self.customView.setupLayoutRulesForPages(self.addExpenseVC.view, secondPage: self.addCategoryVC.view)
}
My custom view:
class __AMVCView: UIView {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var contentView: UIView!
#IBOutlet weak var pageControl: UIPageControl!
override func awakeFromNib() {
super.awakeFromNib()
self.scrollView.pagingEnabled = true
self.scrollView.bounces = true
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.showsVerticalScrollIndicator = false
self.pageControl.numberOfPages = 2
self.contentView.backgroundColor = UIColor.blueColor()
self.scrollView.backgroundColor = UIColor.clearColor()
self.backgroundColor = UIColor.blackColor()
}
func setupLayoutRulesForPages(firstPage: UIView, secondPage: UIView) {
guard self.contentView.subviews.contains(firstPage) && self.contentView.subviews.contains(secondPage)
else {
return
}
let rules = [
"H:|-0-[firstPage]-0-[secondPage]-0-|",
"V:|-0-[firstPage]-0-|",
"V:|-0-[secondPage]-0-|"
]
let views = [
"firstPage" : firstPage,
"secondPage" : secondPage
]
let constraints = NSLayoutConstraint.constraintsWithVisualFormatArray(rules, metrics: nil, views: views)
UIView.disableAutoresizingMasksInViews(firstPage, secondPage)
self.addConstraints(constraints)
// Add the width Autolayout rules to the pages.
let widthConstraint = NSLayoutConstraint(item: firstPage, attribute: .Width, relatedBy: .Equal, toItem: self.scrollView, attribute: .Width, multiplier: 1, constant: 0)
self.addConstraint(widthConstraint)
}
}