There're some good example of implement custom pager view using ScrollView like:
https://github.com/MarcBruins/BWWalkthrough-Xamarin-iOS
https://github.com/ariok/BWWalkthrough
Above sample work well, but if I have a UINavigationController as root contorller, and push above pager view as a child;
when scroll to switch page(drag and move both horizontally and vertically before drop), the whole pager view also moves vertically, this is not as expected.
does any has idea how to disable this vertically move(or bounce) ?
public override void ViewDidLoad(){
...
_pagedViewController.View.Frame = new CGRect(0, topMargin + pagerHeight, frame.Width, frame.Height);
...
//AddChildViewController(_pagedViewController);
View.AddSubview(_pagedViewController.View);
//_pagedViewController.DidMoveToParentViewController(this);
...
}
I've wrap page view inside container controler, and had code above, I've now fix issue by comment out two lines above.
I've found if I change pageview's height, also got vertical scroll problem, but this can be avoid by other fix.
Related
TD;DR
It seems that in some cases systemLayoutSizeFitting does not return the correct height to correctly show / position all subviews of a view. Am I using systemLayoutSizeFitting wrong or is there some other way to avoid this?
Long story:
The XIB file of a UIViewController does not only contain the main view but also a number of other views which are added to the view controllers view at runtime. All these additional views should get the same height when they are added to the view controllers view.
The views might look like this: A simple container view holding some subviews which are stacked on top of each other.
Since the height of the container view should be flexible, the vertical spacing between the bottom button and the lable above it, uses a grater-than constraint.
To give all views the same height, I tried to measure the necessary height of each view using systemLayoutSizeFitting:
#IBOutlet var pageViews: [UIView]!
override func viewDidLoad() {
super.viewDidLoad()
var maxHeight: CGFloat = 0
for pageView in pageViews {
// Add pageView somewhere on view and give it leading, trailing and top
// constraint, but no height constraint yet.
addToView(pageView)
maxHeight = max(maxHeight, pageView.systemLayoutSizeFitting(CGSize(width: view.frame.width, height: UIView.layoutFittingCompressedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel).height)
}
for pageView in pageViews {
// Give all pageViews the same height
pageView.heightAnchor.constraint(equalToConstant: maxHeight).isActive = true
}
}
This does not work, when the label text becomes to long:
In the right example the height is not large enough and thus the button is squeezed. I can counter act this by raising the vertical compression resistance of the button, however in this case the other controls (e.g. the title label) is squeezed...
Why is this? Why does not systemLayoutSizeFitting return a height which is sufficent to show all controls without any squeezing?
Its actually smash button's height when label text is getting bigger . You are setting top and bottom constraints but button height is not declared so when label getting bigger , view basically say "I can reduce button height before updating my height , I have space.Bottom and top constraints are still same , didn't effect."
Giving the constant height constraints of button might be fix your issue.
If you want your view to resist to compression you should use the defaultHigh priority as a verticalFittingPriority instead of fittingSizeLevel.
I want to create a page which has a vertical but no horizontal scroll. It must adjust width of the content and vertical scroll automatically as per screen size.
Something similar to this:
I can not use UITableView since, my page may not have necessarily homogenous elements. It could have a combination of textfields , dropdown etc.
The previous answer was quite right, but not right at all. Indeed I tried to solve this problem using the method described before, but to make it work, I made some adjustments.
Your view's hierarchy has to be as follow :
UIScrollview :
View1
View2
View3
You don't need a container inside the UIScrollview, because apart the fact that it will be an extraview that you don't need, there is the problem that if you use this container-view you will get problem getting touch events in the views added.
So, let's make a step-by-step process:
Add scrollview to your viewController
The first step is to add the scrollview to your viewController, and we can simply do this programmatically in the following way:
UIScrollView scrollView = new UIScrollView();
scrollView.TranslatesAutoresizingMaskIntoConstraints = false;
View.AddSubview(scrollView);
View is the main-view of the viewController you are working in (aka Self.View).
Put attention to set TranslateAutoResizionMaskIntoConstrains property of the scrollview to false, otherwise autoresizing will mess your constraints.
Add constraint (autolayout) to your scrollView
You need to ensure that you layout will adjust for every different iPhone-screen, so simply use auotlayout to pin your scrollView to the viewController main-view (is the View used in the next code sample):
scrollView.TopAnchor.ConstraintEqualTo(View.TopAnchor, 0).Active = true;
scrollView.BottomAnchor.ConstraintEqualTo(View.BottomAnchor, 0).Active = true;
scrollView.LeadingAnchor.ConstraintEqualTo(View.LeadingAnchor, 0).Active = true;
scrollView.TrailingAnchor.ConstraintEqualTo(View.TrailingAnchor, 0).Active = true;
In this way your scrollView is pinned to the bound of the main-view.
Create the view to be added
You need to create the view that you will add to the scrollView:
UIView viewToBeAdded = new UIView();
viewToBeAdded.TranslatesAutoresizingMaskIntoConstraints = false;
viewToBeAdded.Frame = new CGRect(0, 0, UIScreen.MainScreen.Bounds.Width, 200);
We have created a new UIView that setting its frame large as the screen (UIScreen.MainScreen.Bounds.Width) so it won't scroll horizontally, and with an arbitrary height (200 in the sample).
NOTE : even in this case you have to set TranslateAutoResizingMaskProperty to false, otherwise you will get a mess.
Add the view to the scrollView
Next step is to add our new view to the scrollView as follow:
scrollView.AddSubview(view);
Nothing more.
Set constraint for the view added in relation to the scrollView
Once you have added your view you have to said which will her behavior related to the scrollView. We assume that we will add several view to the scrollView, so we have to made a distinction, to the behavior of the FIRST view, the IN-BETWEEN views, and the LAST view.
So to be clear we assume that we are adding only 3 views, so we will have the three different cases.
FIRST VIEW
The important thing is that the first view has to be pinned to the top of the scrollView, we do this as follow :
firstView.TopAnchor.ConstraintEqualTo(scrollView.TopAnchor, 0).Active = true;
and then we set the others constraints:
firstView.WidthAnchor.ConstraintEqualTo(firstView.Bounds.Width).Active = true;
firstView.HeightAnchor.ConstraintEqualTo(firstView.Bounds.Height).Active = true;
IN-BETWEEN VIEW
The in between views (in our sample the secondView) need to be pinned to the previous view added (in our case the first view). So we do as follow:
secondView.TopAnchor.ConstraintEqualTo(firstView.BottomAnchor).Active = true;
So the top of the secondView is pinned to the bottom of the firstView.
And then we add the others constraints:
secondView.WidthAnchor.ConstraintEqualTo(secondView.Bounds.Width).Active = true;
secondView.HeightAnchor.ConstraintEqualTo(secondView.Bounds.Height).Active = true;
LAST VIEW
The last view (in our case the third view) instead needs to be pinned to the bottom of the previousView (in our case the secondView) and to the bottom of the scrollView.
thirdView.TopAnchor.ConstraintEqualTo(secondView.BottomAnchor).Active = true;
thirdView.BottomAnchor.ConstraintEqualTo(scrollView.BottomAnchor).Active = true;
And the usual other constraints for width and eight:
thirdView.WidthAnchor.ConstraintEqualTo(thirdView.Bounds.Width).Active = true;
thirdView.HeightAnchor.ConstraintEqualTo(thirdView.Bounds.Height).Active = true;
In this way the eight of the scrollView will adapt to the eight of the views added, due to the fact that the views inside are pinned to the top and the bottom of the scrollView.
CONCLUSIONS
If you follow these simple instruction you will get everything work. Remember to disable autoResizingMask, as this is on of the common mistake.
Hope it was helpful.
Cheers
In a custom renderer for Xamarin.Forms i've written my UITableViewController like this:
_controller = new InfoFieldItemsTableViewController();
_controller.TableView.SeparatorStyle = UITableViewCellSeparatorStyle.None;
_controller.TableView.SeparatorColor = UIColor.Clear;
_controller.TableView.AllowsSelection = false;
// http://useyourloaf.com/blog/self-sizing-table-view-cells/
_controller.TableView.RowHeight = UITableView.AutomaticDimension;
In my controller i am doing this to register all potential cell candidates:
private void RegisterCells()
{
foreach (var tuple in InfoFieldCellMapping.Map)
{
this.TableView.RegisterNibForCellReuse(tuple.Value.Item1, tuple.Value.Item2);
}
}
public override void ViewDidLoad()
{
RegisterCells();
base.ViewDidLoad();
}
I am doing this in my controller so cells resize themselves depending on how much height they need:
public override nfloat EstimatedHeight(UITableView tableView, NSIndexPath indexPath)
{
return 100;
}
Now all you need to do is create cell files from within your IDE which should be .xib files and design them in the editor using autolayout (so they can adapt to orientation changes automatically).
Within your TableViews datasource all that's left to do is mapping between your data item and it's corresponding cell similar to:
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
var dataItem = Items[indexPath.Row];
var key = ""; // use info from data item to figure out which key identifies your table cell to dequeue the correct kind of cell
var cell = collectionView.DequeueReusableCell(key, indexPath) as UICollectionViewCell;
}
That's all you need really. In my scenario i am mapping fields which may contain different controls for date entries, number entries, long texts, short texts etc etc.
I hope that helps
1.Add Leading,Trailing,Top,Bottom Constraints on scrollView to it'superview.
2.Add UIView as containerView of scrollview and add 6 Constraints from containerView to scrollview as below.
a)Leading b)trailing c)top d)bottom e)Center Horizontally.
3.Make sure top elements in container view must bind to top by adding top constraints and also bind bottom most element to bottom of container view by adding bottom constraints.And also all the items between topmost and bottommost in the container view must be vertically connected to each other so it will define the content size of container view.
it will define the actual content height for scrollview.
and finally define content size for scrollview in code.
As I mentioned here .
Refer to Step 1 and Step 2 ,set constraints on Scrollview and containerView.
I remove the margin between Scrollview and View , and I add some controls on the containerView , so it looks like as below:
Notice
Since we set the containerView's width equal to scrollview's width, the width is fixed, so we can scroll vertically not horizontally.
Height of controls and spaces between them should be set clearly, because the contentSize is auto calculated by adding them. (If contentSize is greater than the height of screen ,the scrollview can be scrolled)
I saw you set those constrains on controls , but you can't scroll down to see the controls out of screen, I think you are missing to set bottom margin on the last control(the downmost one).
Let us do a test.
1. We set the margin (between button and textfield ) to 1000 and don't set bottom margin between the textfield and containerView.
Result : can't scroll down to see the textfield out of screen.
2. Set the margin 1000 and add a bottom margin(10) between textfiled and containerView.
Result: can scroll
Demo Link
I am trying to create a scrollable stack view.
e.g Its hierarchy would be like this
-ScrollView
--StackView
---StackView1
---StackView2
---StackView3
.....
---StackView12
StackView1,StackView2, ...., StackView12 will have dynamic height and same width as of OuterMost Scroll View.
And OuterMost StackView also have same width as of OuterMost Scroll View.
I have set properties of OuterMost StackView as bellow:
Alignment: Fill
Distribution: FillEqually
Spacing: 15
All the sub StackViews (StackView1,StackView2, ...., StackView12) doing well.
I have also added this in my swift code
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
OutMostScrollView.contentSize = CGSize(width: OutMostStackView.frame.width, height: OutMostStackView.frame.height)
}
But I am unable to scroll in the OuterMost StackView.Please anybody guide me the true way to achieve this.
Thank you very much.
I just added the height of 250 in the height parameter of OutMostScrollView and the problem solved. Now It scrolls to the end.
OutMostScrollView.contentSize.height = OutMostStackView.frame.height+250
what I get from this is I think there may be some hidden height other than OutMostStackView.frame.height which is consumed by the view controller's view.
Swift 4.2
I've made an example of scroll in stack view.
It's simple and complete, don't need story board (programmatically).
You can see it in gist below:
đź’» Scroll stackView in Swift
I have a UISplitViewController in my application (MvvmCross / Xamarin iOS) and for some reason I cannot get the content to respect the dimensions of the available view areas.
In the situation shown in the screenshot the master view is hosting a UIViewController with a TableView inside. All the layouts are done with constraints and work fine on their own when running in an iPhone emulator.
As soon as I switching to running on an iPad some custom code I have in my presenter shows this same view in the master panel of a UISplitViewController but in this situation the constraints seem to be ignored and I end up with a view that looks like this:
As you can see the right hand side of the table cell is now way off the viewable area of the master panel of the UISplitViewController.
Both the UITableView and the UITableCell both use View.Frame as their initial size (I've tried View.Bounds as well).
How can I get the cells and / or table to respect the bounds of the UISplitViewController available space?
Thanks to Cheesebarons question I found my solution (cause).
I have a set of methods in a helper class that I use to generate my "default" UIViews.
One of these methods creates my default UITableView:
public static UITableView CreateDefaultTableView(CGRect rect, UITableViewStyle style)
{
var tv = new UITableView(rect, style)
{
AutoresizingMask = UIViewAutoresizing.FlexibleHeight,
SeparatorStyle = UITableViewCellSeparatorStyle.SingleLine,
SeparatorColor = IosConstants.DefaultTableSeparatorColor,
BackgroundColor = IosConstants.DefaultViewBackgroundColor
};
return tv;
}
Changing:
AutoresizingMask = UIViewAutoresizing.FlexibleHeight,
To:
AutoresizingMask = UIViewAutoresizing.All,
Schoolboy error!
How can I have a UIScollView content be the content from a UIView? I want to design the app layout in a UIView and then lay that view into a UIScrollView that is connected to a UIPageControl for pagination. So when the user swipes to the side, the next view is displayed. I have a sort of idea on how I would accomplish this, but I want to get it right without wasting a lot of time.
Heres my DetailViewController:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using UIKit;
using Foundation;
using CoreGraphics;
using CloudKit;
namespace RecordStorePro
{
public partial class DetailViewController : UIViewController
{
public Record DetailRecord { get; set; }
public DetailViewController (IntPtr handle) : base (handle)
{
}
public void SetDetailRecord (Record record)
{
if (DetailRecord != record) {
DetailRecord = record;
// Update the view
ConfigureView ();
}
}
void ConfigureView ()
{
// Update the user interface for the detail item
if (IsViewLoaded && DetailRecord != null) {
//label.Text = DetailRecord.Album;
}
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Perform any additional setup after loading the view, typically from a nib.
NavigationItem.SetLeftBarButtonItem (new UIBarButtonItem(UIBarButtonSystemItem.Stop, (sender, args) => {
NavigationController.PopViewController(true);
}), true);
ConfigureView ();
//this.scrollview.BackgroundColor = UIColor.Gray;
// set pages and content size
scrollview.ContentSize = new SizeF ((float)(scrollview.Frame.Width * 2), (float)(scrollview.Frame.Height));
//this.scrollview.AddSubview ();
this.scrollview.Scrolled += ScrollEvent;
}
private void ScrollEvent(object sender, EventArgs e)
{
this.pagecontrol.CurrentPage = (int)System.Math.Floor(scrollview.ContentOffset.X / this.scrollview.Frame.Size.Width);
}
public override void DidReceiveMemoryWarning ()
{
base.DidReceiveMemoryWarning ();
// Release any cached data, images, etc that aren't in use.
}
}
}
So when I swipe the screen, I want a subview that contains some labels and textfields to come in and replace the original labels and textfields. It works properly so far except I can't figure out how to add the subview, and make it size appropriately to different screen sizes.
EDIT
Heres the problem Im facing now, the views laying in the ScrollView act funny and are about 44f too tall so they let me drag up and down. i tried setting all kinds of constraints as well as manually setting them to -44 smaller with no help. heres a picture of the problem now:
Heres the screenshot of my constraints set.
View A:
View B:
ScrollView:
Nib view:
To do this, you might want to try these steps:
The view that shows the first page will be called View A. The view that shows the second page will be called View B.
Add both views to the Scroll View.
Control-drag from View A to the Scroll View in the sidebar.
Hold down Shift and select Top Space to Superview, Bottom Space to Superview, and Leading Space to Superview. Next, press Return to add those constraints.
Make sure those constraints’ constants are set to 0.
Control-drag from View B to the Scroll View in the sidebar.
Hold down Shift and select Top Space to Superview, Bottom Space to Superview, and Trailing Space to Superview. Next, press Return to add those constraints.
Control-drag from View A to View B in the sidebar.
Select Horizontal Spacing. Make sure its constant is 0 and its Second Item is View A.Trailing and its First Item is View B.Leading.
Control-drag from View A to View B in the sidebar. Select Equal Widths. Make sure the Constant is 0.
Control-drag from View A to the Scroll View in the sidebar. Select Equal Widths. Make sure the constant is set to 0.
In the inspector, check “Paging Enabled.”
Adding subScrollView with no pagingEnabled to each page and controls into subScrollView works! If you add controls directly to scrollview with paging enabled, it seems gesture recogniser's first responder is always scrollview so your controls never gets the event and behave like a disabled!
UIScrollView has a property called “pagingEnabled”
In Interface Builder, resize the scroll view to allow for some space below it for the page control. Next, drag in a UIPageControl from the library, centered below the scroll view. Resize the UIPageControl to take up the full width of the view.