Embed PageViewController in ScrollView - Programmatically Swift 5 - swift

I have a PageViewController which shows 4 four view Controllers horizontally,
Can I embed all this viewControllers in a UIScrollView? It would be easier to manage some tasks.
Till now I tried doing this in PageViewController, conforming the PageViewController to UIScrollViewDelegate:
for view in self.view.subviews {
if let subView = view as? UIScrollView {
subView.delegate = self
subView.isScrollEnabled = true
subView.bouncesZoom = false
}
}
But it actually recognises the scrolling for the single ViewController and not for the entire stack of four VCs.
I'd like to have the contentSize of the ScrollView equal to the entire width of all viewControllers.

You cannot change a UIPageViewController into a scroll view. It already is a scroll view, but has a lot of built-in functionality for memory-managed paging through view controllers.
If your goal is to put 4 view controllers into a scroll view:
create a new (plain) UIViewController
add a UIScrollView and constrain it to all 4 sides
add a UIStackView (Distribution: Fill Equally) to the scroll view
constrain all 4 sides of the stack view to the scroll view's content layout guide
constrain its height to the scroll view's frame layout guide height
add 4 UIContainerViews to the stack view
embed each of your 4 "page" view controllers into the 4 containers
constrain the width of the first container to width of scroll view frame layout guide
You now have a full-view UIScrollView where you can scroll back and forth through your 4 "page" view controllers.

Related

How to get a custom scrollingStackView's height

I have a custom view, it's a scrolling view with a stack view in it. And I added 3 container views inside this stack view to stack and scroll vertically. Inside each container view I add either a UIImage or a UILabel view.
I don't want my scrollview to scroll if all the views are already visible on screen.
For this I need to know the height of my scrollingStackView, so I can compare it with the current screen size.
Here is how I set them up in viewDidLoad:
setupScrollingSV()
setupContainer1()
setupContainer2()
setupContainer3()
setupImageViewInContainer1()
setupImageViewInContainer2()
setupLabelViewInContainer3()
My custom scrolling stack view doesn't have a size, I lay it out programmatically giving left, right, top anchors.
scrollingSV.leftAnchor.constraint(equalTo: stackView.leftAnchor).isActive = true
scrollingSV.rightAnchor.constraint(equalTo: stackView.rightAnchor).isActive = true
scrollingSV.topAnchor.constraint(equalTo: stackView.topAnchor).isActive = true
And there is another stack view that is in safe area of my view, which has this scrolling stack view and another container view ContainerView4.
I layout ContainerView4 like this:
ContainerView4.leftAnchor.constraint(equalTo: stackView.leftAnchor).isActive = true
ContainerView4.rightAnchor.constraint(equalTo: stackView.rightAnchor).isActive = true
ContainerView4.topAnchor.constraint(equalTo: scrollingSV.bottomAnchor).isActive = true
ContainerView4.bottomAnchor.constraint(equalTo: stackView.bottomAnchor).isActive = true
ContainerView4.heightAnchor.constraint(equalToConstant: 100.0).isActive = true
Lastly, when I use debug view hierarchy I do see the height and width for my scrollingSV, and the views inside the containers.
Here is the code I am trying to get a size for the scrollingSV, which returns 0:
print("Scrollview height: \(scrollingSV.contentSize.height )")
I already tried to get the content size by using union as told in this stackoverflow answer: How do I auto size a UIScrollView to fit its content
But this also returns 0.
How can I get this scrolling stack view's size?
Or is it not calculated yet in viewDidLoad?
Thank you
Apparently in viewDidLoad the views were not finished with laying out their subviews. So the height was not meaningful yet.
When I did this check in viewDidAppear, the problem is fixed and I could reach the scrolling stack view's contentSize and frame height.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
scrollingSV.isScrollEnabled = scrollingSV.contentSize.height > scrollingSV.frame.height
}

How to programmatically set constraints when View sizes are different?

Using Xcode 10, iOS 11.4+, Swift 4
I have a series of UIViewControllers that I am navigating through,
each of which is being placed into a ContainerView.
Based on which VC is pushed, I am hiding/showing the Top or Bottom views (shown in gray) in the Main Controller while the ContainerView is always in the middle (light blue).
What I would like to do is set the constraints so that the ContainerView is appropriately sized when the Top or Bottom views are shown/hidden.
For example, when the "Main Menu" is shown, the ContainerView should fill the entire Main Container view (Top and Bottom views will be hidden)
When "Item1" is shown, the Top view will be shown and the Bottom view hidden. Therefore, ContainerView should fill the Main Container view left, right, and bottom, but the ContainerView Top should be constrained to the Top view bottom.
When "Item5" is shown, the Top and Bottom views will also be shown.
Therefore, ContainerView should fill Main Container view left, right, and be constrained to the Top view bottom, and the Bottom view top (as shown in the Main Container)
I've tried using code like this to fill the entire Main view:
NSLayoutConstraint.activate([
ContainerView.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 0),
ContainerView.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: 0),
ContainerView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0),
ContainerView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0)
])
However, the ContainerView never changes, and Xcode gives me a lot of warnings like:
[LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one
you don't want.
Here's another screenshot from the Storyboard:
Here's a link to download my sample project:
https://gitlab.com/whoit/containerviews
How can I correctly modify the constraints to accomplish this?
As I've mentioned in the comment, you should have used UIStackView for your top / bottom views visibility controlling.
You will need a UIStackView with following attributes:
Axis: Vertical
Alignment: Fill
Distribution: Fill
Spacing: As per your need
The stack view will contain the Top View, Container View and Bottom View respectively as its sub views.
You need to pin all the sides (leading, trailing, top & bottom) of this stack view to its superview (view controller's view). And as you need some height constraints for your top & bottom view, you give them as your need.
So basically your stack view is now capable of resizing its sub views when you hide any of them. You just need to perform:
theSubViewNeedsTobeHidden.isHidden = true
I've made a working demo for you that can be found here.
Issues
1) You're adding new constraints all the time, as this lines create a new constraints, each time they're getting called:
ContainerView.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 0)
2) In the beginning, the MainContainerView is not constraint at all
Solution
I would suggest following:
add those four constraints in the storyboard instead
create IBOutlets for the height-constraints of your top and bottom views.
set the constants of those to 0 (when hiding them)
(optional) for sure you can still fade them in/out by setting their alpha as well, and then add the height constant to the completion block.
Note:
Hidden views (alpha = 0 or isHidden = true) are still taken into account for layouting. That means, your constraints are still valid, when the views are hidden. But to make them look like hidden for the layouting as well, you'll then have to set theirs height constants to 0.
Code
#objc func hideControlViews(_ notification: Notification){
let userInfo = notification.userInfo! as! [String : Bool]
//print("Got top view notification: \(String(describing: userInfo["hide"]))")
if (userInfo["hidetop"]!) {
self.topViewHeightConstraint.constant = 0
} else {
self.topViewHeightConstraint.constant = 64
}
if (userInfo["hidebottom"]!) {
self.bottomViewHeightConstraint.constant = 0
} else {
self.bottomViewHeightConstraint.constant = 108
}
self.view.layoutIfNeeded()
}

Buttons outside of UIScrollView are not touchable

I'm facing an annoying problem with UIScrollView that my buttons cannot be touched if they are outside of the scroll view but I dont know how to fix it now
I have tried some ways but no helps so far
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
var contentRect = CGRect.zero
for view in scrollContentView.subviews {
contentRect = contentRect.union(view.frame)
}
for view in scrollContentView.subviews {
contentRect = contentRect.union(view.frame)
}
scrollView.contentSize.height = contentRect.size.height
}
The code above just helps to make the scroll view scrollable
I also attached my sample project in this link
https://drive.google.com/open?id=19U8jecDNQbAnTFbG36KMRxHfaLLcaLDq
I strongly appreciate your advices. Thank you
You have not described your view hierarchy correctly. What you actually have is this:
Scroll view
Content view
Stack view
Buttons
The content view is what is causing the problem. Its height is pinned to the height of the view controller's main view — which is the height of the screen. But of course the stack view with its buttons is taller than the screen, in order to give you something to scroll to. So the lower part of the stack view, and the buttons at the bottom of the stack view, are below the bottom of the content view. Thus they are outside their superview. Thus they are untouchable. A view outside its superview (or its superview, or its superview, all the way up the view hierarchy) is untouchable.

My ScrollView is not scrolling vertically

I'm writing an app in Xcode 9 with Swift 4 and I've added a UIScrollview to a view which is intended to show a jPeg which is 3030 pixels in height. I've added the scrollview to my view and assigned the delegate in Outlets in IB. I've attached the IBOutlet called scrollView to the UIScrollview and added a UIImageView with the jPeg to the UIScrollview. I've set the size of the UIScrollView to 375W and 620H and then set the UIImageView to 375W and 3030H. this should complete the work in Interface builder.
In the Controller I've added UIScrollViewDelegate to the Class and added the code below to ViewDidLoad
//scrollView.delegate = self
scrollView.contentSize = CGSize(width: 375, height: 3040)
I've commented out the delegate line as I've done that in IB. When I run the app, the screen comes upon with the image but when I try to scroll it barely scrolls more than one screen. What have I missed?
You need to add a UIView inside your scrollview and then add that view top/bottom/leading/trailing 0 to scrollview then add your image view inside view and add image view bottom constraint to that view, So your scroll view will work properly.
Please refer below image for more details.
You have to place your scroll view inside a UIView and another UIView inside your scroll view.
Outer view Constraints: Leading,trailing,top and bottom - 0
Scroll view Constraints: Leading,trailing,top and bottom - 0
Inner view Constraints: Leading,trailling and top to Superview, Equal width to Outer view , Bottom space to -250 and Height equals 900(Height of your content)
Set your contentsize now.
You can implement as below image screen shots
//Height Constant of image
#IBOutlet weak var ConstHeight: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 11.0, *) {
self.scrollView.contentInsetAdjustmentBehavior = .never
}else{
self.automaticallyAdjustsScrollViewInsets = false
}
//You can change height on here too
self.ConstHeight.constant = 800
}

Scrolling ScrollView even when ContentView has the same size

I have a ScrollView defined with autolayout : it has a contentView, in which there's some elements. The scrollView has constraints with other views to set it's size, and 4 constraints with the contentView (up, down, left, right, each with constant = 0). The contentView has a defined size, the same size as the scrollView.
I want my scrollView to scroll and bounce (I overrided the scrollViewDidScroll method), but the problem is the scrollView is not scrolling if the contentView has the same size as it (which is logic, since there's no content to scroll, but I want it to bounce).
How can I make my scrollView scrolling ?
Try setting to your scrollView:
scrollView.alwaysBounceHorizontal = true
scrollView.alwaysBounceVertical = true
or in Interface Builder, make sure you've checked options: