Deciding height for UIScrollView with UIWebView inside - ios

I have a detail view where I want to show a title, subtitle and content for articles. I want to be able to use HTML to format the text, so I've used a UIWebView for showing the article body. This works perfectly.
How ever, all of this, is inside a UIScrollView, so my issue is that I have to calculate the height of the UIScrollView?
This is how it works today:
And this is how it looks like in Storyboard:
So what I need to find out, is what is the correct code and syntax to calculate the correct height of the UIScrollView? Amongst several things, I tried [self.scrollView sizeToFit] without luck.
EDIT: Apparently it sets the correct heights with the code below, but seems like the view never updates.
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
// get height of content in webview
CGFloat height = [[webView stringByEvaluatingJavaScriptFromString:#"document.body.scrollHeight;"] floatValue];
// set new frame height
CGRect frame = webView.frame;
frame.size.height = height;
webView.frame = frame; // webview is now correct height
// set new frame height for scrollview (parent of webview)
CGRect scrollFrame = self.scrollView.frame;
scrollFrame.size.height = webView.frame.origin.y + height;
self.scrollView.frame = scrollFrame;
// log to console for cross checking
NSLog(#"new frame: %f, scrollview frame: %f", scrollFrame.size.height, self.scrollView.frame.size.height);
}
The console reports the apparently correct height:
new frame: 582.000000, scrollview frame: 582.000000
And a quick check in Photoshop as well, this seems to be correct:
The summed value of green and blue area is 582 pixels, but the scrollview still just scrolls the 504 pixel area from below the navigation bar to the bottom of the screen (to the bottom of the tab bar).

The webview has internally a scrollview. You can query its size by webview.scrollView.contentSize. You have to wait with this until the webview has finished rendering.
So, in the -webViewDidFinishLoad: delegate method you can get the optimal height of the webView through webView.scrollView.contentSize.height. You can then resize the webView to this height and layout the other views appropriately. If all of this is done in a custom view, the proper way of doing this would probably be to just call [theView setNeedsLayout] and override -layoutSubviews in theView.
You also should set webView.scrollView.alwaysBounceVertically to NO.

I solved the problem.
First of all, just expand the UIWebView to a height higher than the content ever will be (e.g. 2000 pixels).
The delegate method code that makes the magic happen
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
// set height for webiew
webView.autoresizesSubviews = NO;
webView.translatesAutoresizingMaskIntoConstraints = YES;
webView.scrollView.autoresizesSubviews = NO;
webView.scrollView.translatesAutoresizingMaskIntoConstraints = YES;
CGFloat height = [[webView stringByEvaluatingJavaScriptFromString:#"document.getElementById('content').clientHeight;"] floatValue] + 80; // +80 for tabbar and spacing
CGRect frame = webView.frame;
frame.size.height = height;
webView.frame = frame;
// fix height of scroll view as well
self.scrollView.translatesAutoresizingMaskIntoConstraints = YES;
self.scrollView.contentSize = CGSizeMake(320, (self.webView.frame.origin.y + self.webView.frame.size.height));
}

Related

Change the position dynamically

Im new to IOS development , so when i change the width of a UITextFeild dynamically i want the button below to shift up .
i tried using the constrains but it doesn't seem to shift dynamically .
(IBAction)selectStatus:(id)sender {
CGRect frameRect = _textViewDevices.frame;
frameRect.size.height = 10;
self.textViewDevices.frame = frameRect;
any good example of how to achieve that ?
I want to achieve something like the Relative positioning in android .
current box before any action
Try calling layoutIfNeeded after the modifications:
- (IBAction)selectStatus:(id)sender {
CGRect frameRect = _textViewDevices.frame;
frameRect.size.height = 10;
self.textViewDevices.frame = frameRect;
[self.view layoutIfNeeded];
}
If you have a height constraint on the text view, try to set its constant instead of setting the frame height:
- (IBAction)selectStatus:(id)sender {
self.textViewHeightConstraint.constant = 10;
[self.view layoutIfNeeded];
}
Programmatically when the first violet field changes in height, to make all the below views stay next to it, you should update the frame.origin.y properly.
So, for example, the status label should be reframed like this
CGRect frame = statusLabel.frame;
frame.origin.y = firstField.origin.y + firstField.size.height + 5;
statusLabel.frame = frame;
And the same for all below views (I've supposed 5 pixels of space between views)

Find bounds of UIViewController's view not obscured by UINavigationBar, UITabBar, (etc)

I can configure my UIViewController's edgesForExtendedLayout so that it will extend underneath content such as the navigation bar or tab bar. If I do this, is there some way to determine the frame that is not obscured?
As a possible alternative, is there a way for a UIViewController to determine the default contentInset to apply to a UIScrollView it contains?
Use case
I have zoomable UIScrollView containing an image.
When it is fully zoomed out I want to adjust the content inset too allow the content to stay centred (details here). However, my modified insets don't take in to account the insets that the UIViewController applies automatically so that its content isn't obscured by navigation bars, etc.
I also need to compute the minimum zoom for the content – that at which the whole image will be visible and not obscured. To compute this, I need to know the size of the unobscured part of the content view.
You need this
-(CGRect) unobscuredBounds
{
CGRect bounds = [self.view bounds];
return UIEdgeInsetsInsetRect(bounds, [self defaultContentInsets]);
}
-(UIEdgeInsets) defaultContentInsets
{
const CGFloat topOverlay = self.topLayoutGuide.length;
const CGFloat bottomOverlay = self.bottomLayoutGuide.length;
return UIEdgeInsetsMake(topOverlay, 0, bottomOverlay, 0);
}
You could put this in a category for easy reusability.
These methods correctly handle the changes that occur when the view resizes after a rotation – the change to the UINavigationBar size is correctly handled.
Centring Content
To use this to centre content by adjusting insets, you'd do something like this:
-(void) scrollViewDidZoom:(UIScrollView *)scrollView
{
[self centerContent];
}
- (void)centerContent
{
const CGSize contentSize = self.scrollView.contentSize;
const CGSize unobscuredBounds = [self unobscuredBounds].size;
const CGFloat left = MAX(0, (unobscuredBounds.width - contentSize.width)) * 0.5f;
const CGFloat top = MAX(0, (unobscuredBounds.height - contentSize.height)) * 0.5f;
self.scrollView.contentInset = UIEdgeInsetsMake(top, left, top, left);
}
Your content insets will now reflect the default insets that they need (to avoid being covered up) and will also have the insets they need to be nicely centred.
Handling Rotation & Zoom
You probably also want to perform centring when animating between landscape and portrait. At the same time, you might want to adjust your minimum zoom scale so that your content will always fit. Try out something like this:
-(void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self centerContent];
const bool zoomIsAtMinimum = self.scrollView.zoomScale == self.scrollView.minimumZoomScale;
self.scrollView.minimumZoomScale = [self currentMinimumScale];
if(zoomIsAtMinimum)
{
self.scrollView.zoomScale = self.scrollView.minimumZoomScale;
}
}
-(CGFloat) currentMinimumScale
{
const CGFloat currentScale = self.scrollView.zoomScale;
const CGSize scaledContentSize = self.scrollView.contentSize;
const CGSize scrollViewSize = [self unobscuredBounds].size;
CGFloat scaleToFitWidth = currentScale * scrollViewSize.width / scaledContentSize.width;
CGFloat scaleToFitHeight = currentScale * scrollViewSize.height / scaledContentSize.height;
return MIN(scaleToFitWidth, scaleToFitHeight);
}
The willAnimateRotationToInterfaceOrientation:… method is called within the view animation block, so the changes that it applies will lead to nice smooth animated changes as you switch from landscape to portrait.

UIScrollView - content goes out bound when scrolling

I design the scrollview in interface builder like this
It looks good here. But unfortunately when I run it on emulator or device
it becomes
The content in scrollview is expand outside scrollview itself and even though outside UIView that contains this scrollView.
In my viewDidLoad (panel is the container of scrollView )
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
CGFloat adjustPanelHeight = [PTTScreenScaleUtil getAdjustHeight:self.panel.frame.size.height];
CGRect panelRect = self.panel.frame;
panelRect.size.height = adjustPanelHeight;
self.panel.frame = panelRect;
UIImage *image = [UIImage imageNamed:#"panel-background"];
UIGraphicsBeginImageContext(self.panel.frame.size);
[image drawInRect:CGRectMake(0, 0, self.panel.frame.size.width, adjustPanelHeight)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.panel setBackgroundColor:[UIColor colorWithPatternImage:newImage]];
NSLog(#"scrollView Height : %f", self.scrollView.frame.size.height);
NSLog(#"scrollView contentSize Height : %f", self.scrollView.contentSize.height);
// CGRect scrollViewRect = self.scrollView.frame;
// CGRect scrollViewContentRect = self.scrollView.frame;
// NSLog(#"ScrollView Height Before : %f , After : %f", self.scrollView.frame.size.height, [PTTScreenScaleUtil getAdjustHeight:self.scrollView.frame.size.height]);
// scrollViewRect.size.width = 280;
// scrollViewRect.size.height = [PTTScreenScaleUtil getAdjustHeight:270];
// self.scrollView.frame = scrollViewRect;
// [self.detailsLabel sizeToFit];
UIView *view = [[self.scrollView subviews] objectAtIndex:0];
// [view sizeToFit];
// [self.scrollView setContentMode:UIViewContentModeScaleAspectFit];
// NSLog(#"ContentSize Height : %f", view.frame.size.height);
// scrollViewContentRect.size.height = view.frame.size.height;
NSLog(#"Bounds : %f", view.bounds.size.height);
self.scrollView.frame = CGRectMake(10, 10, 280, 270);
self.scrollView.contentSize = CGSizeMake(280, 500);
NSLog(#"Frame Height %f", self.scrollView.frame.size.height);
//[self.scrollView setContentSize: CGSizeMake(280, 1000)];
CGRect termBtnRect = self.termBtn.frame;
CGRect mailBtnRect = self.mailBtn.frame;
CGRect twitterBtnRect = self.twitterBtn.frame;
CGRect fbBtnRect = self.fbBtn.frame;
termBtnRect.origin.y = adjustPanelHeight - 10 - termBtnRect.size.height;
mailBtnRect.origin.y = adjustPanelHeight - 10 - termBtnRect.size.height;
twitterBtnRect.origin.y = adjustPanelHeight - 10 - termBtnRect.size.height;
fbBtnRect.origin.y = adjustPanelHeight - 10 - termBtnRect.size.height;
self.termBtn.frame = termBtnRect;
self.mailBtn.frame = mailBtnRect;
self.twitterBtn.frame = twitterBtnRect;
self.fbBtn.frame = fbBtnRect;
}
All the log return 270.0
PS. the scroll bar is correct even though the content goes outside but the scroll bar is working correctly (stay in the scrollview's frame as arrange in interface builder)
I have no idea how can I solve this.
Anyone help me please.
Thanks you.
Solve it by creating new view controller in interface builder and redo the same process with careful and bingo. It works.
When I compare both two view controller I realise that the wrong one UIScrollView Clip Subviews is unchecked. When check it the problem solve.
I just struggled with this for an hour and had a head smack moment.
In my case, I had a UIView on the scene in the Storyboard. At some point I decided I needed it to be a UIScrollView instead (as opposed to the original plan which was to embed the UIScrollView in a UIView)
I went ahead and changed the class on the UIView to UIScrollView. IB changed it to Scroll View in the Document Outline, I figure I'm good, right?
And then I see the behavior you describe.
At some point it hits me that this isn't sufficient. Apparently adding a UIScrollView via IB does some things differently than adding a UIView and just changing class isn't enough. And this is probably the reason re-doing it from scratch fixed it for you.
So for anyone who runs into this in the future, make sure you added the UIScrollView via IB instead of a UIView
I was having the same problem as described in this post. I tried multiple combinations of solutions that did not work, including:
putting the scroll view inside a view with Clip To Bounds = YES
putting a view inside the scroll view with Clip To Bounds = YES, that then contained my child view
putting a Container View inside the scroll view, and then embedding my subview
rebuilding the Interface Builder files completely
every combination of autosizing mask options systematcially for both the scroll view and container view
clip to bounds enabled or disabled for every single element systematically
The child view in question had previously worked inside a scroll view, but wouldn't in this one case where the content blew outside the bounds of the scroll view.
In the end, I implemented the solution in code as I could find no way to get Interface Builder to co-operate:
// There are two scroll areas on the screen, the left view and the right view.
// We want the right view to contain a scrollable area with another child view controller
// we designed in Interface Builder.
// create a scroll view to fill the right view with a scrollable area
CGSize rightFrameSize = self.rightView.bounds.size;
scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake (0, 0, rightFrameSize.width, rightFrameSize.height)];
scrollView.contentSize = CGSizeMake(640, 1352);
[self.rightView addSubview:scrollView];
// now create our child view controller from Interface Builder and add it to the scroll view
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"CustomerAddress" bundle:[NSBundle mainBundle]];
detailsView = [sb instantiateViewControllerWithIdentifier:#"CustomerDetailsView"];
detailsView.delegate = self;
detailsView.customer = _customer;
[scrollView addSubview:detailsView.view];
You could of course get the scrollView.contentSize from the child view controller you constructed in Interface Builder using scrollView.contentSize = detailsView.view.frame.size.
I have a love/hate relationship with Interface Builder... most days I love it, but some days we argue and I wish we'd never met... :)
only make cliptobound=YES in storyboard if you changed UIView to UIScrollView

UIButton does not respond with UIScrollview Paging

I am running into a problem with the uiscrollview. When I have it as a regular scroll view all the UIButtons work as expected. When I add paging to it the buttons no longer respond. I have a top edge sticking out and they do accept the press from there. They are definitely behind the scroll view but they will scroll on and off the page when it is moved like I want them to. I have read a lot of different responses but most will not provide the answer unless you use gesture controllers. I am attaching the piece of code that creates the paging. can you let me know what I might be missing?
-(void)scollPagingSetup:(float)frameSize {
float numberOfPages;
int maxheight;
[self.view setPagingEnabled:YES];
[self.view setBounces:YES];
UIView *first = [[UIView alloc]initWithFrame:self.view.frame];
[self.view addSubview:first];
maxheight = [UIScreen mainScreen].bounds.size.height;
numberOfPages = ceil(frameSize/maxheight);
for (int i = 1; i < numberOfPages; i++) {
CGRect frame;
frame.origin.x = 0;
frame.origin.y = self.view.frame.size.height * i;
frame.size = self.view.frame.size;
UIView *subview = [[UIView alloc] initWithFrame:frame];
[self.view addSubview:subview];
}
self.view.contentSize = CGSizeMake(self.view.frame.size.width, self.view.frame.size.height *numberOfPages);
}
Thank you in advance for taking a look. I found something that would answer this problem but I was unable to grasp what the real solutions was to the problem. here is the link to that question:
IPhone UIButton doesn't respond in a UIScrollView
The problem is that the content size is not set properly so the "clickable area" (content area's size) is also not set... on your last line of code (line before the last "}") you are setting the scrollView's contentSize's frame to itself which will just be 0... try this instead (I use "first" (the nested view) instead of "self" (the scroll-view itself).
self.view.contentSize = CGSizeMake(firstframe.origin.x, (firstframe.origin.y - 20) * numberOfPages);
Note from question-poster on edited answer:
I made a change to the answer. After I worked with the app some more i was not scrolling fully. I made the change to reflect what I did to fix this and cause a full page scroll. the -20 offset was needed due to the offset of the uiscrollview. This offset can be seen in interface builder.

UIScrollView + change orientation = messed up subviews (iPad)

I want to use a UIScrollView as my main container in the app, enabling me to swipe back and forth between subviews. To achieve this, I created a UIViewController subclass with a UIScrollView IBOutlet:
In the viewDidLoad method I construct the sub-pages:
for (int i= 0; i< pageCount; i++)
{
CGRect frame = self.scrollView.frame;
frame.origin.x = frame.size.width * i;
frame.origin.y = 0;
UIWebView* aWebView= [[UIWebView alloc] initWithFrame:frame];
[self.scrollView addSubview:aWebView];
}
When launching the app (portrait mode), everything works. That is, the UIWebViews are layed out side by side with the correct dimensions, and I can swipe back and forth between them.
When I rotate to landscape, it seems that neither the scrollview size nor the subviews are resized.
I don't know what I should do in order to resize the subviews and the scrollview itself, or at what point in code I should do anything, and I cant seem to find any examples for this.
Anyone know what to do?
[edit] Attempt to adjust sizes as suggested by mahboudz:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * pageCount, self.scrollView.frame.size.height);
for (int i= 0; i< pageCount; i++)
{
CGRect frame = self.scrollView.frame;
frame.origin.x = frame.size.width * i;
frame.origin.y = 0;
UIWebView* view= [[self.scrollView subviews] objectAtIndex:i];
view.frame= frame;
}
}
This kind of does what I want, but has the following issues:
1) one can see the subviews grow to correct screen size upon changing orientation
2) when the current page is, for example, page 2 of 5 pages, the page is not fully visible after orientation was changed, but is off-screen by like 40 pixels
3) I get strange effects depending on whether the app is launched in portrait or landscape mode (simulator), ill try to explain:
When the app is launched in portrait mode:
The shape/border of the subviews looks messed up/offscreen, see screenshots:
http://i53.tinypic.com/21jr76x.png
when I rotate to landscape, everything looks okay, scrolling works superb. even when I rotate back to portrait, everything is great now:
http://i55.tinypic.com/if3iiw.png
When the app is launchend in landscape mode:
I get the same messed up/offscreen glitches as in portrait mode
Switching back and forth between portrait and landscape fixes this for landscape mode
BUT: Portrait mode will have the subviews with the width of the landscape mode, thus subviews are too wide
I tried to fix 1) doing the code above in willRotateToInterfaceOrientation however it completely messed up the layout.
I fixed 2) by adding the following code to didRotateFromInterfaceOrientation:
// update the scroll view to the appropriate page
CGRect frame = self.scrollView.frame;
frame.origin.x = frame.size.width * self.currentPage;
frame.origin.y = 0;
[self.scrollView scrollRectToVisible:frame animated:NO];
Note: current page is determined in scrollViewDidScroll
I dont have any idea how to fix 3)
You would need to reset the frame size, content size and the content offset in order to get the subviews in a proper position.
CGFloat screenHeight =[UIScreen mainScreen].bounds.size.height;
CGFloat screenWidth =[UIScreen mainScreen].bounds.size.width;
self.scrollView.frame = CGRectMake(0, 0, screenWidth, screenHeight);
self.scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * numberOfPages, self.scrollView.frame.size.height);
self.scrollView.contentOffset = CGPointMake(visiblePageBeforeRotation * self.scrollView.bounds.size.width, 0);
This code should be placed in the method
-(void)willRotateToInterfaceOrientation: (UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration
Check as well the answer on this question:
Clean autorotation transitions in a paging UIScrollView
It has good example named Rotolling for rotating UIScrollView with paging enabled.
Hope this helps.
P.S: I am facing a problem on repositioning the center of the UIWebView on the rotation.
You need to implement viewWillRotate/viewDidRotate and make adjustments to our content size and orientation as needed.

Resources