How can you make this view in iOS Swift? - ios

please click here to see which view I have to make
I have to make a map view which is similar to apple maps, and it has description view on top of the map view. The user can slide the description view left and right to see the route description in order.
I have no idea what these are called or how these are made. I really want to google but have no idea which keyword I have to use.
Please Help!

It is pagerview, you can use this library FSPagerView. Or, you can do it yourself by using scrollview or collectionview. Implement the scrollViewDidScroll to detect what is the current page and show the correct view

To make that view shown in picture, all you need to do is
take a UICollectionView and put it on your mapview. Provide UICollectionView a constraints at top, leading and trailing. Also you need yo provide height constraint of about 120-150.
in your ViewController drag and make an outlet of this UICollectionView.
Make your UIViewController follow UICollectionViewDelegate and UICollectionViewDataSource.
create a UICollectionViewCell with a xib file.
register that cell in your UIViewController.
create a datamodel which has properties that you need in order to show the direction data.
create an array in that UIViewController.
use that array as a data source of UICollectionView.
Make sense?

As others have said, UIPageViewController works well:
class ViewController: UIViewController, UIPageViewControllerDataSource {
var mapView: MKMapView!
var data: [String] = {
var data = [String]()
for _ in 0...5 {
data.append("덕원 아파트에서 출발합니다.")
}
return data
}()
override func viewDidLoad() {
super.viewDidLoad()
mapView = MKMapView()
self.view.addSubview(mapView)
mapView.translatesAutoresizingMaskIntoConstraints = false
// pageVC setup
let cardVC = CardViewController(data: data[0])
let pageVC = PageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
pageVC.setViewControllers([cardVC], direction: .forward, animated: false, completion: nil)
pageVC.dataSource = self
// container view for containment
let containerView = UIView()
view.addSubview(containerView)
containerView.backgroundColor = .black
// containment
addChild(pageVC)
containerView.addSubview(pageVC.view)
pageVC.didMove(toParent: self)
containerView.translatesAutoresizingMaskIntoConstraints = false
pageVC.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// mapView
mapView.widthAnchor.constraint(equalTo: view.widthAnchor),
mapView.heightAnchor.constraint(equalTo: view.heightAnchor),
// container view
containerView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8),
containerView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.2),
// pageVC
pageVC.view.widthAnchor.constraint(equalTo: containerView.widthAnchor),
pageVC.view.heightAnchor.constraint(equalTo: containerView.heightAnchor)
])
let pageControl = UIPageControl.appearance()
pageControl.pageIndicatorTintColor = UIColor.gray.withAlphaComponent(0.6)
pageControl.currentPageIndicatorTintColor = .white
pageControl.backgroundColor = .clear
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let info = (viewController as! CardViewController).label.text, var index = data.firstIndex(of: info) else { return nil }
index += 1
if index > data.count {
return nil
}
return CardViewController(data: data[index])
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let info = (viewController as! CardViewController).label.text, var index = data.firstIndex(of: info) else { return nil }
index -= 1
if index <= 0 {
return nil
}
return CardViewController(data: data[index])
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return data.count
}
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
let pageVC = pageViewController.viewControllers![0] as! CardViewController
let t = pageVC.label.text!
return data.firstIndex(of: t)!
}
}
class PageViewController: UIPageViewController {
}
class CardViewController: UIViewController {
var label: UILabel!
var containerView: UIView!
var iconView: IconView!
init(data: String) {
self.label = UILabel()
self.label.text = data
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
let v = UIView()
v.backgroundColor = .black
view = v
}
override func viewDidLoad() {
super.viewDidLoad()
containerView = UIView()
view.addSubview(containerView)
// container view
containerView.addSubview(label)
containerView.translatesAutoresizingMaskIntoConstraints = false
// label
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .black
label.textColor = .white
label.numberOfLines = 0
// icon view
iconView = IconView()
containerView.addSubview(iconView)
iconView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// containerView
containerView.heightAnchor.constraint(equalTo: view.heightAnchor),
containerView.widthAnchor.constraint(equalTo: view.widthAnchor),
// icon view
iconView.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.4),
iconView.heightAnchor.constraint(equalTo: containerView.heightAnchor),
iconView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
// label
label.leadingAnchor.constraint(equalTo: iconView.trailingAnchor),
label.heightAnchor.constraint(equalTo: containerView.heightAnchor),
label.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -10),
])
}
}
class IconView: UIView {
override func draw(_ rect: CGRect) {
let circle = UIBezierPath(arcCenter: self.center, radius: 20, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
UIColor.orange.setFill()
circle.fill()
}
}

Related

Interactive push view controller

I am looking to have an interactive push view controller. So if the user pans from the right edge of the screen, it will pop to the next view controller. I have found this CocoaPods: https://github.com/rickytan/RTInteractivePush, but it is written in Objective-C, so I am unsure how to use it. On my own I have been able to come up with a pan gesture that pushes a view, however it is not interactive:
swipeGesture = UIPanGestureRecognizer(target: self, action:#selector(swiped(_:)))
swipeGesture.delegate = self
view.addGestureRecognizer(swipeGesture)
#objc func swiped(_ gestureRecognizer: UIPanGestureRecognizer) {
let newView = View()
self.navigationController?.pushViewController(newView, animated: true)
}
Any help would be greatly appreciated!
You can do it programmatically with UIPageViewController:
Set your UIPageViewController class:
import UIKit
class MyControllerContainer: UIPageViewController {
// set UIPageViewController transition style
override init(transitionStyle style: UIPageViewController.TransitionStyle, navigationOrientation: UIPageViewController.NavigationOrientation, options: [UIPageViewController.OptionsKey : Any]? = nil) {
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
print("init(coder:) has not been implemented")
}
var pages = [UIViewController]()
var pageControl = UIPageControl()
let initialPage = 0
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setup()
style()
layout()
}
}
Now set style, setup, and layout func:
extension MyControllerContainer {
func setup() {
dataSource = self
delegate = self
pageControl.addTarget(self, action: #selector(pageControlDragged(_:)), for: .valueChanged)
// create an array of viewController
let page1 = ViewController1()
let page2 = ViewController2()
let page3 = ViewController3()
pages.append(page1)
pages.append(page2)
pages.append(page3)
// set initial viewController to be displayed
setViewControllers([pages[initialPage]], direction: .forward, animated: true, completion: nil)
}
func style() {
pageControl.translatesAutoresizingMaskIntoConstraints = false
pageControl.currentPageIndicatorTintColor = .white
pageControl.pageIndicatorTintColor = UIColor(white: 1, alpha: 0.3)
pageControl.numberOfPages = pages.count
pageControl.currentPage = initialPage
}
func layout() {
view.addSubview(pageControl)
NSLayoutConstraint.activate([
pageControl.widthAnchor.constraint(equalTo: view.widthAnchor),
pageControl.heightAnchor.constraint(equalToConstant: 20),
view.bottomAnchor.constraint(equalToSystemSpacingBelow: pageControl.bottomAnchor, multiplier: 1),
])
}
}
set how we change controller when pageControl Dragged:
extension MyControllerContainer {
// How we change controller when pageControl Dragged.
#objc func pageControlDragged(_ sender: UIPageControl) {
setViewControllers([pages[sender.currentPage]], direction: .forward, animated: true, completion: nil)
}
}
after that set UIPageViewController delegate and datasource:
// MARK: - DataSources
extension MyControllerContainer: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let currentIndex = pages.firstIndex(of: viewController) else { return nil }
if currentIndex == 0 {
return nil // stop presenting controllers when swipe from left to right in ViewController1
} else {
return pages[currentIndex - 1] // go previous
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let currentIndex = pages.firstIndex(of: viewController) else { return nil }
if currentIndex == 2 {
print("Last index...")
}
if currentIndex < pages.count - 1 {
return pages[currentIndex + 1] // go next
} else {
return nil // stop presenting controllers when swipe from right to left in ViewController3
}
}
}
// MARK: - Delegates
extension MyControllerContainer: UIPageViewControllerDelegate {
// How we keep our pageControl in sync with viewControllers
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
guard let viewControllers = pageViewController.viewControllers else { return }
guard let currentIndex = pages.firstIndex(of: viewControllers[0]) else { return }
pageControl.currentPage = currentIndex
}
}
Now add your viewControllers, in my case 3:
// MARK: - ViewControllers
class ViewController1: UIViewController {
let mylabel1: UILabel = {
let label = UILabel()
label.text = "Controller 1"
label.textAlignment = .center
label.textColor = .white
label.font = .systemFont(ofSize: 20, weight: .semibold)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemRed
view.addSubview(mylabel1)
mylabel1.heightAnchor.constraint(equalToConstant: 50).isActive = true
mylabel1.widthAnchor.constraint(equalToConstant: view.frame.width).isActive = true
mylabel1.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
mylabel1.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
class ViewController2: UIViewController {
let mylabel2: UILabel = {
let label = UILabel()
label.text = "Controller 2"
label.textAlignment = .center
label.textColor = .white
label.font = .systemFont(ofSize: 20, weight: .semibold)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGreen
view.addSubview(mylabel2)
mylabel2.heightAnchor.constraint(equalToConstant: 50).isActive = true
mylabel2.widthAnchor.constraint(equalToConstant: view.frame.width).isActive = true
mylabel2.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
mylabel2.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
class ViewController3: UIViewController {
let mylabel3: UILabel = {
let label = UILabel()
label.text = "Controller 3"
label.textAlignment = .center
label.textColor = .white
label.font = .systemFont(ofSize: 20, weight: .semibold)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
view.addSubview(mylabel3)
mylabel3.heightAnchor.constraint(equalToConstant: 50).isActive = true
mylabel3.widthAnchor.constraint(equalToConstant: view.frame.width).isActive = true
mylabel3.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
mylabel3.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
This is the result:
The current code is perfect it you have only one viewcontroller up next.
But if you have to 2 or more viewController up next then interactive push is unuseful technique. Vies versa for the interactive pop controller we just have to pop top view form the navigation stack which make sense. Please have a look the the image below which describe the scenario for both push and pop.

How to use UIScrollView to scroll to the top of the first view?

I currently have a UIScrollView with a UIImageView (top image) positioned below the UINavigationBar. However, I want to position the UIImageView at the very top of the screen (bottom image). Is there a way to implement this?
What I've tried so far: I added a UIScrollView extension (source) that is supposed to scroll down to the view parameter provided, but it hasn't worked for me.
extension UIScrollView {
// Scroll to a specific view so that it's top is at the top our scrollview
func scrollToView(view:UIView, animated: Bool) {
if let origin = view.superview {
// Get the Y position of your child view
let childStartPoint = origin.convert(view.frame.origin, to: self)
// Scroll to a rectangle starting at the Y of your subview, with a height of the scrollview
self.scrollRectToVisible(CGRect(x:0, y:childStartPoint.y,width: 1,height: self.frame.height), animated: animated)
}
}
// Bonus: Scroll to top
func scrollToTop(animated: Bool) {
let topOffset = CGPoint(x: 0, y: -contentInset.top)
setContentOffset(topOffset, animated: animated)
}
// Bonus: Scroll to bottom
func scrollToBottom() {
let bottomOffset = CGPoint(x: 0, y: contentSize.height - bounds.size.height + contentInset.bottom)
if(bottomOffset.y > 0) {
setContentOffset(bottomOffset, animated: true)
}
}
}
class MealDetailsVC: UIViewController {
private var mealInfo: MealInfo
init(mealInfo: MealInfo) {
self.mealInfo = mealInfo
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
scrollView.scrollToView(view: iv, animated: false) // used extension from above
}
lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
return scrollView
}()
lazy var iv: UIImageView = {
let iv = UIImageView()
iv.image = Image.defaultMealImage!
iv.contentMode = .scaleAspectFill
return iv
}()
}
extension MealDetailsVC {
func setupViews() {
addBackButton()
addSubviews()
autoLayoutViews()
constrainSubviews()
}
fileprivate func addBackButton() {
...
}
#objc func goBack(sender: UIBarButtonItem) {
...
}
fileprivate func addSubviews() {
view.addSubview(scrollView)
scrollView.addSubview(iv)
}
fileprivate func autoLayoutViews() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
iv.translatesAutoresizingMaskIntoConstraints = false
}
fileprivate func constrainSubviews() {
NSLayoutConstraint.activate([
scrollView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor),
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
NSLayoutConstraint.activate([
iv.topAnchor.constraint(equalTo: scrollView.topAnchor),
iv.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
iv.heightAnchor.constraint(equalTo: iv.widthAnchor, multiplier: 0.6)
])
}
}
This may help.
scrollView.contentInsetAdjustmentBehavior = .never
For more information,
This property specifies how the safe area insets are used to modify the content area of the scroll view. The default value of this property is UIScrollViewContentInsetAdjustmentAutomatic.
Don't set a height anchor and add a imageView centerXAnchor equal to ScrollView centerXAnchor constraint. set imageView.contentMode to .scaleAspectFit

Swift: UIStackView Overlapping all Views instead of Stacking them Vertically

I am trying to create a statistics page for my app that will have various charts that are created dynamically depending on the type of data the user has. To do this, I am stacking multiple ViewControllers according to this tutorial: https://swiftwithmajid.com/2019/02/27/building-complex-screens-with-child-viewcontrollers/
I am running into an issue where the ViewController's View is added to the main StackView as an arrangedSubView, but instead of it stacking the views vertically and allowing me to scroll through them all, it just stacks them on top of each other in the z-direction.
Here is the StackViewController Code:
class StackViewController: UIViewController {
private let scrollView = UIScrollView()
private let stackView = UIStackView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.addSubview(stackView)
setupConstraints()
stackView.axis = .vertical
}
private func setupConstraints() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
stackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
stackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
stackView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor)
])
}
}
extension StackViewController {
func add(_ child: UIViewController) {
addChild(child)
stackView.addArrangedSubview(child.view)
print(child.view!)
child.didMove(toParent: self)
}
func remove(_ child: UIViewController) {
guard child.parent != nil else {
return
}
child.willMove(toParent: nil)
stackView.removeArrangedSubview(child.view)
child.view.removeFromSuperview()
child.removeFromParent()
}
}
Here is where I create each View Controller and add it to the StackViewController.
For now, I run a loop and add copies of a single view controller over and over:
class PrototypeViewController: StackViewController {
override func viewDidLoad() {
super.viewDidLoad()
for _ in 0...10 {
setupUI()
}
}
private func setupUI() {
let storyboard = UIStoryboard(name: "ConsistencyGraph", bundle: .main)
let consistencyGraphVC = storyboard.instantiateViewController(identifier: "ConsistencyGraphVC") as! ConsistencyGraphVC
add(consistencyGraphVC)
consistencyGraphVC.setupUI(name: "sessionName", consistencyPercentage: 30, ballsHit: 10)
}
}
Here is the View Controller Code:
class ConsistencyGraphVC: UIViewController {
#IBOutlet weak var mainView: UIView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var ballsHitLabel: UILabel!
#IBOutlet weak var pieChartView: PieChartView!
override func viewDidLoad() {
super.viewDidLoad()
}
open func setupUI(name: String, consistencyPercentage: Double, ballsHit: Int) {
displayName(name:name)
drawPieChart(consistencyPercentage: consistencyPercentage)
displayBallsHit(ballsHit: ballsHit)
}
private func displayName(name: String) {
let prefix = "Consistency: "
let title = prefix + name
titleLabel.text = title
}
private func displayBallsHit(ballsHit: Int) {
ballsHitLabel.text = String(ballsHit)
}
private func drawPieChart(consistencyPercentage: Double) {
let maxPercent:Double = 100
let remainingPercent = maxPercent - consistencyPercentage
let dataEntry = [PieChartDataEntry(value: consistencyPercentage, data: String(consistencyPercentage)), PieChartDataEntry(value: remainingPercent, data: nil)]
let dataSet = PieChartDataSet(entries: dataEntry)
let chartData = PieChartData(dataSet: dataSet)
let color1 = randomColor()
let color2 = randomColor()
dataSet.colors = [color1, color2]
pieChartView.data = chartData
}
private func randomColor() -> UIColor {
let red = Double(arc4random_uniform(256))
let green = Double(arc4random_uniform(256))
let blue = Double(arc4random_uniform(256))
let color = UIColor(red: CGFloat(red/255), green: CGFloat(green/255), blue: CGFloat(blue/255), alpha: 1)
return color
}
}
At first, I thought it was because the View Controller sizes might be ambiguous. I then hardcoded the width and height of each View Controller, but still no luck.
Any and all help is much appreciated! I'm at a loss as to how this is even possible.
Thank you in advance!
I have finally found the solution!
I needed to add:
child.view.heightAnchor.constraint(equalToConstant: child.view.frame.size.height).isActive = true
to the StackViewController here:
func add(_ child: UIViewController) {
addChild(child)
child.view.heightAnchor.constraint(equalToConstant: child.view.frame.size.height).isActive = true
stackView.addArrangedSubview(child.view)
child.didMove(toParent: self)
}
I am not sure why this is the case. I had already hard coded the height of the view, but the stackView also wanted me to constrain it before adding the view.
I hope this helps someone in the future! I was beating my head against a wall for ages...
You forgot some required constraints for your StackView inside ScrollView.
You only have trailing, top, and bottom which are not enough.
The correct one:
stackView.leadingAnchor
stackView.trailingAnchor
stackView.topAnchor
stackView.bottomAnchor
stackView.widthAnchor

UIPageViewController within a UIScrollView appears fullscreen even though CGRect is set - returns to correct frame values as soon as user scrolls

I have a UIPageViewController named SwipingPhotosController where the user swipes horizontally to view images. I have implemented this SwipingPhotosController within a UIScrollView by giving it CGRect values. Then I write a function that basically does the stretchy header effect of zooming in & out when the user scrolls up or down.
Everything works except that when I tried to add another view beneath the SwipingPhotosController, as soon as the controller is loaded, the image appears full screen. As soon as I scroll slightly, it all returns to the accurate position.
This is what I get when I press run in simulator - a full screen blown out pageviewcontroller:
Here the view goes back to normal as soon as I scroll slightly
Note: This bug occurs only when I add the nameLabel beneath the imageView (swipingPhotosController.view)
class ProfileController: UIViewController, UIScrollViewDelegate {
var user: User! {
didSet{
swipingPhotosController.user = user
}
}
lazy var scrollProfileView: UIScrollView = {
let sv = UIScrollView()
sv.delegate = self
sv.backgroundColor = TDGSettings
sv.alwaysBounceVertical = true
sv.contentInsetAdjustmentBehavior = .never
return sv
}()
let nameLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 12, weight: .bold)
label.textColor = .white
label.numberOfLines = 2
label.textAlignment = .left
return label
}()
let swipingPhotosController = SwipingPhotosController(transitionStyle: .scroll, navigationOrientation: .horizontal)
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = TDGSettings
setupViews()
}
fileprivate func setupViews() {
view.addSubview(scrollProfileView)
scrollProfileView.fillSuperview()
let imageView = swipingPhotosController.view!
scrollProfileView.addSubview(imageView)
let blurEffect = UIBlurEffect(style: .systemThinMaterialDark)
let visualEffectView = UIVisualEffectView(effect: blurEffect)
view.addSubview(visualEffectView)
visualEffectView.anchor(top: view.topAnchor, leading: view.leadingAnchor, bottom: view.safeAreaLayoutGuide.topAnchor, trailing: view.trailingAnchor)
scrollProfileView.addSubview(nameLabel)
nameLabel.anchor(top: imageView.bottomAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor, padding: .init(top: 16, left: 16, bottom: 0, right: 16))
nameLabel.text = "First Name"
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let imageView = swipingPhotosController.view!
imageView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.width)
}
//STRETCHY HEADER EFFECT
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let changeY = -scrollView.contentOffset.y
var width = view.frame.width + changeY * 2
width = max(view.frame.width, width)
let imageView = swipingPhotosController.view!
imageView.frame = CGRect(x: min(0, -changeY), y: min(0, -changeY), width: width, height: width)
}
}
As requested added code for SwipingPhotosController
import Foundation
import LBTATools
class SwipingPhotosController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var user: User! {
didSet{
controllers = user.swipingUrls.map({ (url) -> UIViewController in
let photoController = PhotosController(imageUrl: url)
return photoController
})
setViewControllers([controllers.first!], direction: .forward, animated: false, completion: nil)
}
}
var controllers = [UIViewController]()
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
delegate = self
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let index = self.controllers.firstIndex(where: {$0 == viewController}) ?? 0
if index == 0 {return nil}
return controllers[index - 1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let index = self.controllers.firstIndex(where: {$0 == viewController}) ?? 0
if index == controllers.count - 1 {return nil}
return controllers[index + 1]
}
}
class PhotosController: UIViewController {
let imageView = UIImageView()
init(imageUrl: String) {
if let url = URL(string: imageUrl){
imageView.sd_setImage(with: url)
}
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
imageView.contentMode = .scaleAspectFill
view.addSubview(imageView)
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.fillSuperview()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The problem is that you are setting swipingPhotosController's frame in viewWillLayoutSubviews() --- but the views have not yet been laid out.
You need to do that in viewDidLayoutSubviews() (did not will).
However, viewDidLayoutSubviews() gets called many times, particularly since you are changing the frame again in scrollViewDidScroll().
So, you need to set a flag to only set the frame in viewDidLayoutSubviews() once (or again, if the scrollView frame has changed).
Not being sure how or when you were setting your SwipingPhotosController's properties, I did it this way to test:
// add a class property
var savedScrollViewWidth: CGFloat = 0.0
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if scrollProfileView.frame.width != savedScrollViewWidth {
savedScrollViewWidth = scrollProfileView.frame.width
let imageView = swipingPhotosController.view!
imageView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.width)
}
}
See if that gets your layout working correctly.

UIPageViewController not calling delegate methods (Swift 5)

I have an extremely simple UIPageViewController setup in my project. When I create my page view controller and navigate to it, everything seems to work properly and it loads the first page properly. However, when I try to swipe or switch to other pages in the array of ViewControllers, my page controller does not call its delegate methods to do so.
Here is how I am instantiating and navigating to the page view controller from the parent view:
let tutorialPageVC = TareTutorialPageViewController()
let nc = UINavigationController(rootViewController: tutorialPageVC)
nc.modalPresentationStyle = .fullScreen
nc.definesPresentationContext = true
nc.setNavigationBarHidden(true, animated: true)
self.present(nc, animated: true, completion: nil)
And here is my page view controller class:
import UIKit
class TareTutorialPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var tutorialPages = [UIViewController]()
let pageControl = UIPageControl()
override init(transitionStyle style: UIPageViewController.TransitionStyle, navigationOrientation: UIPageViewController.NavigationOrientation, options: [UIPageViewController.OptionsKey : Any]? = nil) {
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:])
self.dataSource = self
self.delegate = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
let initialTutorialPage = 0
let tutorialPage1 = TutorialPage1ViewController()
let tutorialPage2 = TutorialPage2ViewController()
let tutorialPage3 = TutorialPage3ViewController()
self.tutorialPages.append(tutorialPage1)
self.tutorialPages.append(tutorialPage2)
self.tutorialPages.append(tutorialPage3)
setViewControllers([tutorialPages[initialTutorialPage]], direction: .forward, animated: true, completion: nil)
self.pageControl.frame = CGRect()
self.pageControl.currentPageIndicatorTintColor = UIColor.white
self.pageControl.pageIndicatorTintColor = UIColor.lightGray
self.pageControl.numberOfPages = self.tutorialPages.count
self.pageControl.currentPage = initialTutorialPage
self.view.addSubview(self.pageControl)
self.pageControl.translatesAutoresizingMaskIntoConstraints = false
self.pageControl.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -5).isActive = true
self.pageControl.widthAnchor.constraint(equalTo: self.view.widthAnchor, constant: -20).isActive = true
self.pageControl.heightAnchor.constraint(equalTo: self.view.heightAnchor, constant: 20).isActive = true
self.pageControl.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
//Might not need any of this if we only want the user to move forward??
if let viewControllerIndex = self.tutorialPages.firstIndex(of: viewController)
{
if viewControllerIndex == 0
{
return self.tutorialPages.last
}
else
{
return self.tutorialPages[viewControllerIndex-1]
}
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if let viewControllerIndex = self.tutorialPages.firstIndex(of: viewController)
{
if viewControllerIndex < self.tutorialPages.count - 1
{
return self.tutorialPages[viewControllerIndex + 1]
}
else
{
//Navigation to go to scale tare settings here...
//For now just returns to first page of page view
return self.tutorialPages.first
}
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if let viewControllers = pageViewController.viewControllers {
if let viewControllerIndex = self.tutorialPages.firstIndex(of: viewControllers[0])
{
self.pageControl.currentPage = viewControllerIndex
}
}
}
}
Here is an example of one my extremely simple view controllers that are shown by the page view controller:
import UIKit
class TutorialPage1ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.black
let label = UILabel()
label.text = "Tutorial page 1"
label.textColor = UIColor.white
self.view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 50).isActive = true
label.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 20).isActive = true
}
}
The problem is not that the delegate funcs are not being called, the problem is that you are completely overlaying your UIPageViewController with a UIPageControl.
You can confirm this by adding this line:
// existing line
self.view.addSubview(self.pageControl)
// add this line
self.pageControl.backgroundColor = .orange
Change your constraint setup like this:
// completely remove these lines
//self.pageControl.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -5).isActive = true
//self.pageControl.widthAnchor.constraint(equalTo: self.view.widthAnchor, constant: -20).isActive = true
//self.pageControl.heightAnchor.constraint(equalTo: self.view.heightAnchor, constant: 20).isActive = true
//self.pageControl.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
pageControl.widthAnchor.constraint(equalTo: g.widthAnchor, constant: -20.0),
pageControl.centerXAnchor.constraint(equalTo: g.centerXAnchor),
pageControl.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -5.0),
])
Now you should see the page control at the bottom, and you'll be able to swipe through the pages.
By the way, UIPageViewController has a "built-in" UIPageControl that you might find works better anyway.

Resources