Make transition between two Menus with UIScrollView - ios

I want to have 2 menus (the other one will be the same as the one shown in the screenshot, but it will contain other items).
Each menu is in its own UIStackView.
I've decided to rule out the UIPageViewController and instead use UIScrollView because it's the behaviour I want.
How can I put both menus inside of UIScrollView and make a nice animation?

Are you avoiding UIPageViewController because of the complexity? Because if you are there is a much easier way to accomplish the same gesture controlled transition using UIScrollView's pagingEnabled property
Based on what I see in the screenshots...
var size = CGSize()
size.height = self.view.frame.height
size.width = self.view.frame.width * 2
let scrollView = UIScrollView()
scrollView.frame = self.view.frame
scrollView.contentSize = size
scrollView.pagingEnabled = true
scrollView.showsHorizontalScrollIndicator = false
nav.center.y = scrollView.frame.height / 2
nav.center.x = scrollView.frame.width / 2
nav2.center.y = scrollView.frame.height / 2
nav2.center.x = nav.center.x + scrollView.frame.width
scrollView.addSubview(nav1)
scrollView.addSubview(nav2)
self.view.addSubview(scrollView)

#dsieczko did fantastic job answering my question. I'm not experienced with AutoLayout, so that was the problem. My final code looks something like this:
import UIKit
class MainMenuViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var nav2: UIStackView!
#IBOutlet var nav: UIStackView!
#IBOutlet var pageControl: UIPageControl!
override func viewDidLoad() {
super.viewDidLoad()
/* MAIN MENUS */
var size = CGSize()
size.height = self.nav.frame.height
size.width = self.view.frame.width * 2
scrollView.frame = self.view.frame
scrollView.contentSize = size
scrollView.pagingEnabled = true
scrollView.showsHorizontalScrollIndicator = false
nav.center.y = scrollView.frame.height / 2
nav.center.x = self.view.frame.width * 1.5
nav2.center.y = scrollView.frame.height / 2
nav2.center.x = nav.center.x + scrollView.frame.width + 400
scrollView.addSubview(nav)
scrollView.addSubview(nav2)
self.view.addSubview(scrollView)
/* MAIN MENUS */
// Change the Navigation Bar Color to Red
navigationController!.navigationBar.barTintColor = UIColor(red: 0.79, green: 0.19, blue: 0.19, alpha: 1)
// Change the Navigation Bar Font to Lato
self.navigationController?.navigationBar.titleTextAttributes =
[NSForegroundColorAttributeName: UIColor.whiteColor(),
NSFontAttributeName: UIFont(name: "Lato-Semibold", size: 20)!]
}
}
And my layout something like this:
If anyone encounters a problem like this, I will gladly help with my exp with this.

Related

IOS Swift4 Not able to scroll a ScrollView

I hv been trying to make a scrollview scroll, just to the extent that the scrollview is supposed to show. However, I am not able to. This is my code.
func setupMainView() {
// This is where the image view and other UIViews which are supposed to go in the contentview are set up
self.setupImagesView()
self.setupView1()
self.setupView2()
self.setupView3()
self.setupView4()
self.scrollView = UIScrollView()
self.scrollView.backgroundColor = UIColor.white
self.view.addSubview(self.scrollView)
self.automaticallyAdjustsScrollViewInsets = false
self.scrollView.contentInset = UIEdgeInsets.zero
self.scrollView.scrollIndicatorInsets = UIEdgeInsets.zero;
self.contentView = UIView()
self.scrollView.layer.masksToBounds = false
self.scrollView.backgroundColor = UIColor.white
self.scrollView.layer.borderWidth = 0
self.scrollView.layer.borderColor = UIColor.white.cgColor
self.view.addSubview(scrollView)
self.contentView.addSubview(imagesScrollView)
self.contentView.addSubview(view1)
self.contentView.addSubview(view2)
self.contentView.addSubview(view3)
self.contentView.addSubview(view4)
self.scrollView.addSubview(contentView)
self.scrollView.contentInset = UIEdgeInsets.zero
self.scrollView.scrollIndicatorInsets = UIEdgeInsets.zero;
var scrollViewHeight:CGFloat = 0.0;
for _ in self.scrollView.subviews {
scrollViewHeight += view.frame.size.height
}
var newHeight = scrollViewHeight * 1.1 + offset + 100
scrollView.contentSize = CGSize(width:screenWidth, height:newHeight)
scrollView.reloadInputViews()
}
The views are getting loaded etc, but I am not manage the scroll. It somehow either too little or too much.
Now, I tried setting the height of contentSize to scrollViewHeight and double of that etc. What I notice is that there is no predictability of how much it will scroll. Change from 1.1 to 1.6 .. there is too much whitescreen below the views, change it to 1.1 or 1.2 it does not even scroll to the bottom.
Note, everything has been set up programmatically, without storyboard etc.
Also note that I need to support all IOS devices with version > 10.
Am a little lost here. What am I doing wrong?
This is a very old way of configuring a scroll view - you should be using auto-layout.
And, you're doing a number of things wrong...
First, we'll assume you are setting frames of the various subviews in code you haven't shown.
However, the code you have shown creates a scrollView and adds it to self.view -- but you never set the frame of the scroll view.
Also, this part of your code:
for _ in self.scrollView.subviews {
scrollViewHeight += view.frame.size.height
}
you've added several views as subviews of contentView, then added contentView as the only subview of scrollView.
And... you are trying to increment scrollViewHeight by the height of your root view instead of the height of the scrollView's subviews.
So, scrollViewHeight will only be the height of self.view.
What you probably want to do is sum the heights of contentView.subviews:
var contentViewHeight: CGFloat = 0
for v in contentView.subviews {
contentViewHeight += v.frame.height
}
contentView.frame.size.height = contentViewHeight
then set the scrollView's contentSize.height to the height of contentView's frame.
Here is a very, very basic example, using explicitly set frame sizes -- again, though, you should start using auto-layout:
class SimpleScrollViewController: UIViewController {
var imagesScrollView: UIView!
var view1: UIView!
var view2: UIView!
var view3: UIView!
var view4: UIView!
var contentView: UIView!
var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
setupMainView()
}
func setupMainView() {
// This is where the image view and other UIViews which are supposed to go in the contentview are set up
self.setupImagesView()
self.setupView1()
self.setupView2()
self.setupView3()
self.setupView4()
self.scrollView = UIScrollView()
// let's use a color other than white so we can see the frame of the scrollView
self.scrollView.backgroundColor = UIColor.cyan
self.view.addSubview(self.scrollView)
self.automaticallyAdjustsScrollViewInsets = false
self.scrollView.contentInset = UIEdgeInsets.zero
self.scrollView.scrollIndicatorInsets = UIEdgeInsets.zero;
self.contentView = UIView()
self.contentView.backgroundColor = UIColor.lightGray
self.scrollView.layer.masksToBounds = false
self.scrollView.layer.borderWidth = 0
self.scrollView.layer.borderColor = UIColor.white.cgColor
self.view.addSubview(scrollView)
self.contentView.addSubview(imagesScrollView)
self.contentView.addSubview(view1)
self.contentView.addSubview(view2)
self.contentView.addSubview(view3)
self.contentView.addSubview(view4)
self.scrollView.addSubview(contentView)
self.scrollView.contentInset = UIEdgeInsets.zero
self.scrollView.scrollIndicatorInsets = UIEdgeInsets.zero;
var contentViewHeight: CGFloat = 0
for v in contentView.subviews {
contentViewHeight += v.frame.height
}
contentView.frame.size.height = contentViewHeight
// don't know what you're doing here....
//scrollView.reloadInputViews()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// here is where you know the frame of self.view
// so, make the scroll view cover the entire view
scrollView.frame = view.frame
// now, make contentView width equal to scrollView width
contentView.frame.size.width = scrollView.frame.size.width
// set the scrollView's content size
scrollView.contentSize = contentView.frame.size
}
func setupImagesView() -> Void {
imagesScrollView = UIView()
imagesScrollView.backgroundColor = .red
imagesScrollView.frame = CGRect(0, 0, 300, 100)
}
func setupView1() -> Void {
view1 = UIView()
view1.backgroundColor = .green
view1.frame = CGRect(20, imagesScrollView.frame.maxY, 300, 200)
}
func setupView2() -> Void {
view2 = UIView()
view2.backgroundColor = .blue
view2.frame = CGRect(40, view1.frame.maxY, 300, 250)
}
func setupView3() -> Void {
view3 = UIView()
view3.backgroundColor = .yellow
view3.frame = CGRect(60, view2.frame.maxY, 200, 275)
}
func setupView4() -> Void {
view4 = UIView()
view4.backgroundColor = .orange
view4.frame = CGRect(80, view3.frame.maxY, 200, 100)
}
}
If I remember correctly you need to in order for scroll view to work you need to implement a couple of delegate methods. You also need a couple of properties set.
contentSize is one
and I think min and max size
see: https://developer.apple.com/documentation/uikit/uiscrollviewdelegate
also see Stanford University's
Paul Hagarty Developing IOS 11 apps with swift episode 9 for loads of information on UIScrollView
https://www.youtube.com/watch?v=B281mrPUGjg
seek to about 31mins for the scroll view information.
This may help in the setup of UIScrollView programatically:
https://sheikhamais.medium.com/how-to-use-the-new-uiscrollview-programmatically-baf270ee9b4

my initial problems with UIScrollView now appear to be related to autolayout

For my first challenge using UIScrollView I modified this example to make UIScrollView display not just another background colour but another UIView and UILabel on each page. But I could have just as easily chosen to display objects like UITableView, UIButton or UIImage.
Potentially, UIScrollView could be much more than a giant content view where users scroll from one part to the next, e.g., some pages might have a UIButton that takes a user to a specific page, the same way we use books.
Code Improvements
My question has evolved since I first posted it. Initially the labels piled up on page 1 (as shown below) but this has now been corrected. I also included this extension to make the font larger.
Further improvement ?
As the code evolved I became more aware of other issues e.g. iPhone 5 images (below) appear differently on iPhone 7 where the UILabel is centred but not the UIView. So my next challenge is possibly to learn how to combine UIScrollView with Autolayout. I invite anyone to spot other things that might be wrong.
ViewController.swift (corrected)
import UIKit
class ViewController: UIViewController,UIScrollViewDelegate {
let scrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
var views = [UIView]()
var lables = [UILabel]()
var colors:[UIColor] = [UIColor.red, UIColor.magenta, UIColor.blue, UIColor.cyan, UIColor.green, UIColor.yellow]
var frame: CGRect = CGRect.zero
var pageControl: UIPageControl = UIPageControl(frame: CGRect(x: 50, y: 500, width: 200, height: 50))
override func viewDidLoad() {
super.viewDidLoad()
initialiseViewsAndLables()
configurePageControl()
scrollView.delegate = self
self.view.addSubview(scrollView)
for index in 0..<colors.count {
frame.origin.x = self.scrollView.frame.size.width * CGFloat(index)
frame.size = self.scrollView.frame.size
self.scrollView.isPagingEnabled = true
views[index].frame = frame
views[index].backgroundColor = colors[Int(index)]
views[index].layer.cornerRadius = 20
views[index].layer.masksToBounds = true
lables[index].frame = frame
lables[index].center = CGPoint(x: (view.frame.midX + frame.origin.x), y: view.frame.midY)
lables[index].text = String(index + 1)
lables[index].defaultFont = UIFont(name: "HelveticaNeue", size: CGFloat(200))
lables[index].textAlignment = .center
lables[index].textColor = .black
let subView1 = views[index]
let subView2 = lables[index]
self.scrollView .addSubview(subView1)
self.scrollView .addSubview(subView2)
}
print(views, lables)
self.scrollView.contentSize = CGSize(width: self.scrollView.frame.size.width * CGFloat(colors.count), height: self.scrollView.frame.size.height)
pageControl.addTarget(self, action: Selector(("changePage:")), for: UIControlEvents.valueChanged)
}
func initialiseViewsAndLables() {
// Size of views[] and lables[] is linked to available colors
for index in 0..<colors.count {
views.insert(UIView(), at:index)
lables.insert(UILabel(), at: index)
}
}
func configurePageControl() {
// Total number of available pages is based on available colors
self.pageControl.numberOfPages = colors.count
self.pageControl.currentPage = 0
self.pageControl.backgroundColor = getColour()
self.pageControl.pageIndicatorTintColor = UIColor.black
self.pageControl.currentPageIndicatorTintColor = UIColor.green
self.view.addSubview(pageControl)
}
func getColour() -> UIColor {
let index = colors[pageControl.currentPage]
return (index)
}
func changePage(sender: AnyObject) -> () {
scrollView.setContentOffset(CGPoint(x: CGFloat(pageControl.currentPage) * scrollView.frame.size.width, y: 0), animated: true)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageNumber = round(scrollView.contentOffset.x / scrollView.frame.size.width)
pageControl.currentPage = Int(pageNumber)
pageControl.backgroundColor = getColour()
}
}
Extension
extension UILabel{
var defaultFont: UIFont? {
get { return self.font }
set { self.font = newValue }
}
}
The centre point of the lable on each frame must be offset by the origin of the content view (as Baglan pointed out). I've modified the following line of code accordingly.
lables[Int(index)].center = CGPoint(x: (view.frame.midX + frame.origin.x), y: view.frame.midY)

How to zoom ImageView inside PageViewController swift

I am using a PageViewController which loads multiple images. I can slide through the images one by one. I now want to zoom that ImageView, how can I achieve it. Thanks in advance.
I have this in my viewDidLoad
var vWidth = self.view.frame.width
var vHeight = self.view.frame.height
var scrollImg: UIScrollView = UIScrollView()
scrollImg.delegate = self
scrollImg.frame = CGRectMake(0, 0, vWidth, vHeight)
scrollImg.backgroundColor = UIColor(red: 90, green: 90, blue: 90, alpha: 0.90)
scrollImg.alwaysBounceVertical = false
scrollImg.alwaysBounceHorizontal = false
scrollImg.showsVerticalScrollIndicator = true
scrollImg.flashScrollIndicators()
scrollImg.minimumZoomScale = 1.0
scrollImg.maximumZoomScale = 10.0
self.view.addSubview(scrollImg)
scrollImg.addSubview(contentImageView!)
And then the delegate of the scrollview
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return self.contentImageView
}
Now the image gets kind of zoomed abnormally, focusing mostly on the top. And not able to pan it.

Swift UIScrollView - strange padding

I need to make the flowers image flipping. Images must be with the same height, but the width to set automatically. I want them to scroll right and left
Here is my code:
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
var images = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
for i in 1...3 {
images.append(UIImage(named: "bild-0\(i).jpg")!)
}
var i: CGFloat = 0
var origin: CGFloat = 0
let height: CGFloat = scrollView.bounds.height
for image in images {
let imageView = UIImageView(frame: CGRectZero)
imageView.frame.size.height = height
imageView.image = image
imageView.sizeToFit()
imageView.frame.origin.x = origin
println(imageView.frame.size.width)
println(imageView.frame.origin.x)
println(imageView.frame.size.height)
println("asd")
origin = origin + imageView.frame.size.width
i++
scrollView.addSubview(imageView)
}
scrollView.contentSize.width = origin
scrollView.bounces = false
scrollView.pagingEnabled = false
}
}
Storyboard:
Problem (Padding from top! - Red color - is a background for UIScrollView):
Images are 765x510 300x510 and so on
UIScrollView height is 170
This is caused by scrolling insets:
Click your ViewController on Storyboard and go to file inspector, and you should see this dialog:
Untick the Adjust Scroll View Insets.

iOS Tinder/Twitter like slider paging navigation and menu

I'm looking for examples/tutorials/framework explaining how to do a navigation bar/controller which slide to left and right like Tinder.app and Twitter.app
I'm not talking about the faces swiping thing of Tinder, I'm talking about the top menu and the views we can slide entirely to left or right to go smoothly to other screens of the app like profile, moments, etc
I'm looking around but not find anything really interesting until then, I hope you can point me out something.
I'm afraid that the complete solution to this is quite a bit beyond the scope of a single question.
However in the interest of trying to help you I think it's worth looking into this - That's a link to Cocoa Controls, a website which people build ready to go controls you can just drop into your app. (it's quite a cool site really).
That particular link is to MSSlidingPanelController. Which I think is exactly what you are looking for. The source code is clearly visible so you can see exactly what's required to get the effect you are looking for.
Here are a few other examples. Hope this helps.
MSSlidingPanelController is not what you are looking for. These are "drawer views", which only allows user to swipe to a certain drawer.
TwitterPagingViewer and SwiftPagingNav is exactly like the one on Twitter, only more complicated.
Tinder seems to be using a UIPageViewController with hidden dots, which is done by deleting these methods:
presentationCountForPageViewController
presentationIndexForPageViewController
Here is a good tutorial:
https://www.youtube.com/watch?v=8bltsDG2ENQ
Here is a great repo:
https://github.com/goktugyil/EZSwipeController
If you need it in Swift, I've created this one
(it also works on any screen resolution vs just iPhone 4/5/5s like the other example)
https://github.com/aubrey/SwiftPagingNav
class PageViewController: UIViewController, UIScrollViewDelegate {
var scrollView:UIScrollView!
var pageControl:UIPageControl!
var navbarView:UIView!
var navTitleLabel1:UILabel!
var navTitleLabel2:UILabel!
var navTitleLabel3:UILabel!
var view1:UIView!
var view2:UIView!
var view3:UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.lightGrayColor()
//Creating some shorthand for these values
var wBounds = self.view.bounds.width
var hBounds = self.view.bounds.height
// This houses all of the UIViews / content
scrollView = UIScrollView()
scrollView.backgroundColor = UIColor.clearColor()
scrollView.frame = self.view.frame
scrollView.pagingEnabled = true
scrollView.showsHorizontalScrollIndicator = false
scrollView.delegate = self
scrollView.bounces = false
self.view.addSubview(scrollView)
self.scrollView.contentSize = CGSize(width: self.view.bounds.size.width * 3, height: hBounds/2)
//Putting a subview in the navigationbar to hold the titles and page dots
navbarView = UIView()
self.navigationController?.navigationBar.addSubview(navbarView)
//Paging control is added to a subview in the uinavigationcontroller
pageControl = UIPageControl()
pageControl.frame = CGRect(x: 0, y: 35, width: 0, height: 0)
pageControl.backgroundColor = UIColor.whiteColor()
pageControl.numberOfPages = 3
pageControl.currentPage = 0
pageControl.currentPageIndicatorTintColor = UIColor(red:0.325, green:0.667, blue:0.922, alpha: 1)
pageControl.pageIndicatorTintColor = UIColor.whiteColor()
self.navbarView.addSubview(pageControl)
//Titles for the nav controller (also added to a subview in the uinavigationcontroller)
//Setting size for the titles. FYI changing width will break the paging fades/movement
var titleSize = CGRect(x: 0, y: 8, width: wBounds, height: 20)
navTitleLabel1 = UILabel()
navTitleLabel1.frame = titleSize
navTitleLabel1.text = "Home"
navTitleLabel1.textAlignment = NSTextAlignment.Center
self.navbarView.addSubview(navTitleLabel1)
navTitleLabel2 = UILabel()
navTitleLabel2.frame = titleSize
navTitleLabel2.text = "Discover"
navTitleLabel2.textAlignment = NSTextAlignment.Center
self.navbarView.addSubview(navTitleLabel2)
navTitleLabel3 = UILabel()
navTitleLabel3.frame = titleSize
navTitleLabel3.text = "Activity"
navTitleLabel3.textAlignment = NSTextAlignment.Center
self.navbarView.addSubview(navTitleLabel3)
//Views for the scrolling view
//This is where the content of your views goes (or you can subclass these and add them to ScrollView)
view1 = UIView()
view1.backgroundColor = UIColor(red:0.325, green:0.667, blue:0.922, alpha: 1)
view1.frame = CGRectMake(0, 0, wBounds, hBounds)
self.scrollView.addSubview(view1)
self.scrollView.bringSubviewToFront(view1)
//Notice the x position increases per number of views
view2 = UIView()
view2.backgroundColor = UIColor(red:0.231, green:0.529, blue:0.757, alpha: 1)
view2.frame = CGRectMake(wBounds, 0, wBounds, hBounds)
self.scrollView.addSubview(view2)
self.scrollView.bringSubviewToFront(view2)
//Notice the x position increases yet again (wBounds * 2)
view3 = UIView()
view3.backgroundColor = UIColor(red:0.529, green:0.600, blue:0.647, alpha: 1)
view3.frame = CGRectMake(wBounds * 2, 0, wBounds, hBounds)
self.scrollView.addSubview(view3)
self.scrollView.bringSubviewToFront(view3)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
navbarView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 44)
}
func scrollViewDidScroll(scrollView: UIScrollView) {
var xOffset: CGFloat = scrollView.contentOffset.x
//Setup some math to position the elements where we need them when the view is scrolled
var wBounds = self.view.bounds.width
var hBounds = self.view.bounds.height
var widthOffset = wBounds / 100
var offsetPosition = 0 - xOffset/widthOffset
//Apply the positioning values created above to the frame's position based on user's scroll
navTitleLabel1.frame = CGRectMake(offsetPosition, 8, wBounds, 20)
navTitleLabel2.frame = CGRectMake(offsetPosition + 100, 8, wBounds, 20)
navTitleLabel3.frame = CGRectMake(offsetPosition + 200, 8, wBounds, 20)
//Change the alpha values of the titles as they are scrolled
navTitleLabel1.alpha = 1 - xOffset / wBounds
if (xOffset <= wBounds) {
navTitleLabel2.alpha = xOffset / wBounds
} else {
navTitleLabel2.alpha = 1 - (xOffset - wBounds) / wBounds
}
navTitleLabel3.alpha = (xOffset - wBounds) / wBounds
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
var xOffset: CGFloat = scrollView.contentOffset.x
//Change the pageControl dots depending on the page / offset values
if (xOffset < 1.0) {
pageControl.currentPage = 0
} else if (xOffset < self.view.bounds.width + 1) {
pageControl.currentPage = 1
} else {
pageControl.currentPage = 2
}
}
}

Resources