UIScrollView's does not scroll AFTER rendering WKWebView content - xamarin

Thank you for looking at my question. Currently, I have several input fields and a WKWebView embedded in a UIScrollView. Before any events fire, all the subviews fit inside the scroll view with no issue. I'm dynamically setting the WK's height based on document.body.scrollHeight which is captured in the DidFinishNavigation delegate located in WKNavigationDelegate. After the WK's height is set, the WK extends past the view-able content. Here's the code which I'm trying to force the scrollview to resize itself.
[Export("webView:didFinishNavigation:")] public async void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
//get the webView's initial height
var initialHeight = webView.Frame.Height;
//get height of HTML's document.body.scrollHeight
var contentHeight = await GetContentHeight();
//create new frame for webview
CGRect newWebViewFrame = new CGRect(webView.Frame.X, webView.Frame.Y, webView.Frame.Width, contentHeight);
//set webview's frame
webView.Frame = newWebViewFrame;
//get the difference of webView's initial height and webView's current height
var differenceInHeight = contentHeight - initialHeight;
//create new cgrect and set the height to svMain's height + the difference in height of the HTML document
CGRect newScrollViewFrame = new CGRect(0, 0, svMainScroller.Frame.Width, svMainScroller.Frame.Height + differenceInHeight);
//set MainScroller's frame
svMainScroller.Frame = newScrollViewFrame;
//force scrolling
svMainScroller.ScrollEnabled = true;
//scrolling should be handled in the main scroller
webView.ScrollView.ScrollEnabled = false;
svMainScroller.SizeToFit();
}
The desired effect is to have the scroll view be able to scroll to the end of the newly defined height. Any tips on how would I go about doing that would be greatly appreciated.

Updating the frame of the scroll view was the problem. My guess is that if the frame is big enough to contain all of the contents, then there's no need to scroll. So, I updated the scroll view's ContentSize instead of updating its frame.
svMainScroller.ContentSize = new CGSize(View.Frame.Width, View.Frame.Height + differenceInHeight);
Also, as a side note, verify that wkwebview is added as a subview to the scroll view and not the main view.

Related

Xamarin.Forms Page Animation

I'm trying to make an animation for my page. I want that animation to get fired only when the page is first loaded.
My animation code is working, sample:
public async Task BackgroundFlyUp()
{
var destinationRect = new Rectangle(0, Application.Current.MainPage.Height * (1 - FoodViewsConstants.FLY_UP_END),
Application.Current.MainPage.Width, Application.Current.MainPage.Height * FoodViewsConstants.FLY_UP_END);
await FlyUp.LayoutTo(destinationRect, FoodViewsConstants.FLY_UP_SPEED, Easing.Linear);
await ContentGrid.FadeTo(1, FoodViewsConstants.CONTENT_FADE_SPEED);
}
Although I don't want my animation to get fired every time OnAppearing is fired, I've tried to add the animation code in my OnAppearing method but it doesn't work right. (the fade is working but the FlyUp not) and being an async method, it can't be placed in the constructor. Can you guys help me out?
Based on my test, I guess you do not get the correct from destinationRect. The Application.Current.MainPage.Width and Application.Current.MainPage.Height would get the width and height except on initial load when the MainPage hadn't been presented yet.
You could use the screen size instead.
public async Task BackgroundFlyUp()
{
// Get Metrics
var mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
// Width (in pixels)
var width = mainDisplayInfo.Width;
// Height (in pixels)
var height = mainDisplayInfo.Height;
var destinationRect = new Rectangle(0, height * (1 - FoodViewsConstants.FLY_UP_END),
width, height * FoodViewsConstants.FLY_UP_END);
await FlyUp.LayoutTo(destinationRect, 5000, Easing.Linear);
await ContentGrid.FadeTo(1, FoodViewsConstants.CONTENT_FADE_SPEED);
}

Top part of a tableView specific color even if refreshing

I've run into a simple problem, that I can not solve even after looking everywhere..
I made a grey table view, and at the top I have a cell with white background.
Is it possible to whenever the user refreshes, make it also white (on the top)?
Try this code
let refresh = UIRefreshControl()
let backgroundColor = UIColor.red
refresh.backgroundColor = backgroundColor
refresh.addTarget(self, action: #selector(self.refreshs), for: .valueChanged)
tableView.addSubview(refresh)
var frame = tableView.bounds
frame.origin.y = -frame.size.height
let backgroundView = UIView(frame: frame)
backgroundView.autoresizingMask = .flexibleWidth
backgroundView.backgroundColor = backgroundColor // background color pull to refresh
tableView.insertSubview(backgroundView, at: 0)

UIPageViewController with Scroll Transition Style automatically adds Safe Area insets for iPhone X

I'm developing image gallery like slider using UIPageViewController and I'm having troubles with UIPageViewController automatic insets in Scroll transition style mode.
Here is my layout:
UIViewController with UIContainerView (magenta background)
UIPageViewController linked to the container (from #1)
List of dynamically created view UIViewController(s) within the page controller (from #2), full width-height views (1. orange, 2. red, 3. green)
It used to work fine for a long time and continue to work with iOS 11 unless it's rendered on iPhone X device with safe area:
I've checked a lot of various options and was able to confirm that it's related specifically to the Scroll mode of the Page Controller. If I switch to PageCurl transition style - it works as expected (full height):
The Page Controller doesn't expose a lot of options to control this behavior for the scroll mode and I wasn't able to "hack" it as well by searching the controls tree and modifying various insets and frame and contentSize related properties. What I can clearly see is that once view controller is created, my scroll view contentSize and frame is 34px smaller then the container frame
> view.frame
{{X=0,Y=0,Width=375,Height=732}}
Bottom: 732
Height: 732
IsEmpty: false
Left: 0
Location: {{X=0, Y=0}}
Right: 375
Size: {{Width=375, Height=732}}
Top: 0
Width: 375
X: 0
Y: 0
> scroll.frame
{{X=-5,Y=0,Width=385,Height=698}}
Bottom: 698
Height: 698
IsEmpty: false
Left: -5
Location: {{X=-5, Y=0}}
Right: 380
Size: {{Width=385, Height=698}}
Top: 0
Width: 385
X: -5
Y: 0
> scroll.contentSize
{{Width=1155, Height=698}}
Height: 698
IsEmpty: false
Width: 1155
I've also set up my autolayout constraints to be linked to superview rather than safe area:
Here is my code for the Home Controller and all the rest is set in a storyboard (alert: C# Xamarin syntax)
private List<UIViewController> viewControllers;
public HomePageViewController (IntPtr handle) : base ( handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var child1 = new UIViewController();
child1.View.BackgroundColor = UIColor.Orange;
var child2 = new UIViewController();
child2.View.BackgroundColor = UIColor.Red;
var child3 = new UIViewController();
child3.View.BackgroundColor = UIColor.Green;
this.viewControllers = new List<UIViewController>
{
child1,
child2,
child3,
};
this.SetViewControllers(new UIViewController[] { child1 }, UIPageViewControllerNavigationDirection.Forward, false, null);
this.GetNextViewController = (c, r) =>
{
var current = this.viewControllers.IndexOf(this.ViewControllers[0]);
if (current >= this.viewControllers.Count - 1)
return null;
return this.viewControllers[current + 1];
};
this.GetPreviousViewController = (c, r) =>
{
var current = this.viewControllers.IndexOf(this.ViewControllers[0]);
if (current <= 0)
return null;
return this.viewControllers[current - 1];
};
}
How can I force my children view controllers to have full height (equals to the frame height of the parent container)?
I think you can solve this issue using code and custom layout. I mean create your UIPageViewController and insert its view to your UIViewController in code not on storyboard. I think you
should override UIViewController.viewDidLayoutSubviews() and set your rects "manually" (at least the one of the UIPageViewController.) Well, when you do it in code, sometimes you even don't need to override UIViewController.viewDidLayoutSubviews() because the template by Apple itself didn't do this. I think because any created view has translatesAutoresizingMaskIntoConstraints = true. So you can also follow this approach.
There is an example when you create a new project and state it is a page based app.
Here is the template if you want (WARNING: This is a part of a template by Apple itself)
var pageViewController: UIPageViewController?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Configure the page view controller and add it as a child view controller.
self.pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
self.pageViewController!.delegate = self
let startingViewController: DataViewController = self.modelController.viewControllerAtIndex(0, storyboard: self.storyboard!)!
let viewControllers = [startingViewController]
self.pageViewController!.setViewControllers(viewControllers, direction: .forward, animated: false, completion: {done in })
self.pageViewController!.dataSource = self.modelController
self.addChildViewController(self.pageViewController!)
self.view.addSubview(self.pageViewController!.view)
// Set the page view controller's bounds using an inset rect so that self's view is visible around the edges of the pages.
var pageViewRect = self.view.bounds
if UIDevice.current.userInterfaceIdiom == .pad {
pageViewRect = pageViewRect.insetBy(dx: 40.0, dy: 40.0)
}
self.pageViewController!.view.frame = pageViewRect
self.pageViewController!.didMove(toParentViewController: self)
}
You can extend this functionality by extending
I had a similar issue that only happened in the X sizes and after hours of trails and errors I got it fixed.
I am not sure if it's applicable for you or not but the way I have my page view controller VCs is that each VC has an image filling its background. I have 3 pages. Scrolling for the first time looked normal but when I would reverse scroll from page 2 to 1 or from 1 to 0, page 1's image would show around 40 pixels from the side when it should be completely hidden (similar to your screenshots).
So to fix it I had to either set the images to Aspect Fit or clips to bounds = true. I used the latter because it worked better for the UI.
I achieved to have my UIPageViewController display with full screen height with the following code :
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let contentScrollView = view.subviews.first(where: { $0 is UIScrollView }) else { return }
contentScrollView.frame.size.height = view.frame.height
}

Xamarin horizontal view with buttons

any ideas how to prepare such an element as at the picture ?
I need 5 buttons to be there so i could swipe through them but only 3 one them are visible all the time. I need this to work on android and ios in xamarin forms.
Try this Add ScrollView and set it's orientation to horizontal
ScrollView = new ScrollView
{
Orientation = ScrollOrientation.Horizontal
};
//ScrollView.Scrolled += ScrollView_Scrolled;
ItemsStackLayout = new StackLayout
{
Orientation = StackOrientation.Horizontal,
Padding = new Thickness(0),
Spacing = 0,
HorizontalOptions = LayoutOptions.FillAndExpand
};
ScrollView.Content = ItemsStackLayout;
you can set the width of the elements inside 1/3 of the view width
FYI I use the same in my app

Set Background Image of a view with stretch to fit in Xamarin

I'm new with Xamarin. I'm actually trying to set the background image of a view and stretch it.
The image is a 2048x1536 pixels png.
nfloat vpHeight = View.Bounds.Height;
nfloat vpWidth = View.Bounds.Width;
Console.WriteLine(vpWidth);
Console.WriteLine(vpHeight);
The above code will return me 1024x768 (it's a landscape position).
var img = UIImage.FromFile("pages/p1.png");
UIImageView imgView = new UIImageView(img);
imgView.ContentMode = UIViewContentMode.ScaleAspectFit;
var prevPage = new UIView()
{
Frame = new CoreGraphics.CGRect(0, 0, vpWidth, vpHeight)
};
prevPage.Add(imgView);
this is the code where I set the background, but the result is just the half of the image in x and y just like the image bellow:
So, how to make the image to adjust to the width and height of the view ?
ty !!
I would create an UIImageView as a background like so:
var img = UIImage.FromFile("pages/p1.png");
UIImageView imgView = new UIImageView(img);
imgView.ContentMode = UIViewContentMode.ScaleAspectFit;
then add this to whatever view you are using.
ContentMode can be used like so:
Update
I would add it to the prevPage like so:
var prevPage = new UIView()
{
Frame = new CoreGraphics.CGRect(0, 0, vpWidth, vpHeight)
};
var img = UIImage.FromFile("pages/p1.png");
UIImageView imgView = new UIImageView(new CGRect(0,0,vpWidth,vpHeight));
imgView.Image = img;
imgView.ContentMode = UIViewContentMode.ScaleAspectFit; // or ScaleAspectFill
prevPage.Add(imgView);
Also its worth noting that using the View.Bounds to position the view is bit clunky. I would take a look into Autolayout as you will encounter problems on different devices and orientations. These are some good tutorials on Autolayout they might be native code but you are looking for the relationships of the views rather than the code.
Raywenderlich tutorial
Other Tutorial
Any probs with autolayout just ask another question.
I would recommend you stay away from FromPatternImage unless you are really using a pattern.
For the lowest memory consumption and best UI performance, this is what I do:
1st) Resize your image using an image context to match the size of the view:
UIGraphics.BeginImageContext(View.Frame.Size);
UIImage.FromBundle("bg.jpg").Draw(View.Bounds);
var bgImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
2nd) Display the resized image in a UIImageView and send it to the lowest Z-order:
var uiImageView = new UIImageView(View.Frame);
uiImageView.Image = bgImage;
View.AddSubview(uiImageView);
View.SendSubviewToBack(uiImageView);

Resources