I'm trying to performSegue() with an animation from left:
class SegueFromLeft: UIStoryboardSegue {
override func perform() {
let src = self.source
let dst = self.destination
src.view.superview?.insertSubview(dst.view, aboveSubview: src.view)
dst.view.transform = CGAffineTransform(translationX: -src.view.frame.size.width, y: 0)
UIView.animate(withDuration: 0.25,
delay: 0.0,
options: .curveEaseInOut,
animations: {
dst.view.transform = CGAffineTransform(translationX: 0, y: 0)
},
completion: { finished in
src.present(dst, animated: false, completion: nil)
}
)
}
But somehow I get the following error for this line: src.present(dst, animated: false, completion: nil), when performSegue is being executed:
Thread 1: EXC_BAD_ACCESS (code=2, address=0x16b607fc0)
I can see the animation, but as soon as the new view is being displayed - the app crashes.
PageViewController in case it helps:
class LeasingTutorialViewController: UIPageViewController, UIPageViewControllerDataSource {
lazy var viewControllerList:[UIViewController] = {
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc1 = sb.instantiateViewController(withIdentifier: "leasingPageOne")
let vc2 = sb.instantiateViewController(withIdentifier: "leasingPageTwo")
return [vc1, vc2]
}()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
if let firstViewController = viewControllerList.first as? LeasingPageOneViewController {
self.setViewControllers([firstViewController], direction: .forward, animated: true, completion: nil)
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let vcIndex = viewControllerList.firstIndex(of: viewController) else { return nil }
let previousIndex = vcIndex - 1
guard previousIndex >= 0 else { return nil }
guard viewControllerList.count > previousIndex else { return nil }
return viewControllerList[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let vcIndex = viewControllerList.firstIndex(of: viewController) else { return nil }
let nextIndex = vcIndex + 1
guard viewControllerList.count != nextIndex else { return nil }
guard viewControllerList.count > nextIndex else { return nil }
return viewControllerList[nextIndex]
}
}
Edit:
It's called in a different ViewController ("goToLeasingTutorial") under some conditions:
Another important note: As soon as I restart the app, it works fine again.
#IBAction func goToLeasing(_ sender: Any) {
if leasingTutorialSeen == false && leasingInStartScreen == true {
performSegue(withIdentifier: "goToLeasingTutorial", sender: self)
} else if leasingTutorialSeen == true && leasingInStartScreen == true {
buttonHamburgerTapped(self)
let parentVC = self.parent as! RootPageViewController
parentVC.setViewControllers([parentVC.viewControllerList[1]], direction: .forward, animated: true, completion: nil)
} else if leasingTutorialSeen == true && leasingInStartScreen == false {
performSegue(withIdentifier: "goToLeasing", sender: self)
}
}
Related
I try to implement a back button for my controllers.
My Navigation Controller implementation inside of the Container Controller:
func configureMainController(){
let mainController = MainController()
mainController.delegate = self
mainController.backDelegate = self
centerController = UINavigationController(rootViewController: mainController)
view.addSubview(centerController.view)
addChild(centerController)
centerController.didMove(toParent: self)
}
My Back Delegate:
extension ContainerController: BackDelegate {
func handleBack() {
print("ok")
centerController.navigationController?.popViewController(animated: true)
}
}
Back button inside of the Main Controller:
navigationItem.rightBarButtonItem = UIBarButtonItem(image: image2?.withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(backAction))
#objc func backAction() -> Void {
backDelegate?.handleBack()
}
How I push the Controller that I need to pop:
DispatchQueue.main.async {
let vc = ScienceController(collectionViewLayout: UICollectionViewFlowLayout())
vc.modalPresentationStyle = .fullScreen
self.navigationController?.pushViewController(vc, animated: true)
}
Here is my Main Controller with relevant functions:
class MainController: UIViewController {
let tabBarCnt = UITabBarController()
var delegate: MainControllerDelegate?
var backDelegate: BackDelegate?
override func viewDidLoad() {
super.viewDidLoad()
createTabBarController()
}
#objc func backAction() -> Void {
//self.navigationController?.popViewController(animated: true)
backDelegate?.handleBack()
}
func checkIfUserIsLoggedIn(){
if Auth.auth().currentUser?.uid == nil{
performSelector(inBackground: #selector(handleLogout), with: nil)
}
}
#objc func handleLogout(){
do {
try Auth.auth().signOut()
} catch let logoutError {
print(logoutError)
}
DispatchQueue.main.async {
// UIView usage
let loginController = LoginController()
loginController.mainController = self
loginController.modalPresentationStyle = .fullScreen
self.present(loginController, animated: true, completion: nil)
}
}
func createTabBarController() {
let firstVc = Controller1()
firstVc.title = "vc1"
firstVc.view.backgroundColor = UIColor.white
firstVc.tabBarItem = UITabBarItem.init(title: "vc1", image: nil, tag: 0)
let secondVc = Controller2()
secondVc.title = "vc2"
secondVc.view.backgroundColor = UIColor.white
secondVc.tabBarItem = UITabBarItem.init(title: "vc2", image: nil, tag: 1)
let thirdVc = Controller3()
thirdVc.title = "vc3"
thirdVc.view.backgroundColor = UIColor.white
thirdVc.tabBarItem = UITabBarItem.init(title: "vc3", image: nil, tag: 2)
let controllerArray = [firstVc, secondVc, thirdVc]
tabBarCnt.viewControllers = controllerArray.map{
UINavigationController.init(rootViewController: $0)}
//tabBarCnt.selectedIndex = 1
self.view.addSubview(tabBarCnt.view)
}
}
Here is my Container Controller:
class ContainerController: UIViewController {
// MARK: - Properties
var menuController: MenuController!
var centerController: UINavigationController!
var isExpanded = false
// MARK: - Init
override func viewDidLoad() {
super.viewDidLoad()
configureMainController()
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .darkContent
}
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation{
return .slide
}
override var prefersStatusBarHidden: Bool{
return isExpanded
}
// MARK: - Handlers
func configureMainController(){
let mainController = MainController()
mainController.delegate = self
mainController.backDelegate = self
centerController = UINavigationController(rootViewController: mainController)
view.addSubview(centerController.view)
addChild(centerController)
centerController.didMove(toParent: self)
}
func configureMenuController(){
if menuController == nil{
menuController = MenuController()
menuController.delegate = self
view.insertSubview(menuController.view, at: 0)
addChild(menuController)
menuController.didMove(toParent: parent.self)
}
}
func animatePanel(shouldExpand: Bool, menuOption: MenuOption?){
if shouldExpand{
//show
UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseInOut, animations: {
self.centerController.view.frame.origin.x = self.centerController.view.frame.width - 80
}, completion: nil)
}
else{
//hide
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: {
self.centerController.view.frame.origin.x = 0
}) { (_) in
guard let menuOption = menuOption else {return}
self.didSelectMenuOption(menuOption: menuOption)
}
}
animateStatusBar()
}
func didSelectMenuOption(menuOption: MenuOption){
switch menuOption {
case .Profile:
let controller = ProfileController()
present(UINavigationController(rootViewController: controller), animated: true, completion: nil)
case .Settings:
let controller = SettingsController()
//controller.username = "pasha"
present(UINavigationController(rootViewController: controller), animated: true, completion: nil)
case .Logout:
handleLogout()
}
}
func animateStatusBar(){
UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseInOut, animations: {
self.setNeedsStatusBarAppearanceUpdate()
}, completion: nil)
}
#objc func handleLogout(){
do{
try Auth.auth().signOut()
DispatchQueue.main.async {
UIApplication.shared.keyWindow?.rootViewController = LoginController()
}
//UIApplication.shared.keyWindow?.rootViewController?.dismiss(animated: true, completion: nil)
} catch let logoutError {
print(logoutError)
}
}
}
extension ContainerController: MainControllerDelegate {
func handleMenuToggle(forMenuOption menuOption: MenuOption?) {
if !isExpanded {
configureMenuController()
}
isExpanded = !isExpanded
animatePanel(shouldExpand: isExpanded, menuOption: menuOption)
}
}
extension ContainerController: BackDelegate {
func handleBack() {
print("ok")
centerController.popViewController(animated: true)
}
}
The print("ok") statement works but not centerController.navigationController?.popViewController(animated: true). I also tried self.navigationController?.popViewController(animated: true) and It does not work too...
Since UINavigationController is subclass of UIViewController it has navigationController property on it. You should not be using navigationController property present on UINavigationController. So your code should be
centerController.popViewController(animated: true)
Edit:
As "Sylvan D Ash" mentioned you are pushing and popping from 2 different controllers.
self.navigationController?.pushViewController(vc, animated: true)
need to be replaced with
centerController.pushViewController(vc, animated: true)
Im my GMSAUtocompleteController I have two textfield but they display same location when clicked
extension RideAddDetailsViewController: GMSAutocompleteViewControllerDelegate {
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
locationTextField.text = place.name
destinationTextField.text = place.name
dismiss(animated: true, completion: nil)
}
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
// Handle the error
print("Error: ", error.localizedDescription)
}
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
// Dismiss when the user canceled the action
dismiss(animated: true, completion: nil)
}
You can use tag to separate them.
if textField.isEqual(locationTextField)
{
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.view.tag = 1 // assign the tag you want
autocompleteController.delegate = self
present(autocompleteController, animated: true, completion: nil)
}
else if textField.isEqual(destinationTextField)
{
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.view.tag = 2 // assign the tag you want
autocompleteController.delegate = self
present(autocompleteController, animated: true, completion: nil)
}
Now you can separate the value from delegate method like this and assign to the textfield.
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
if viewController.view.tag == 1
{
locationTextField.text = place.name
}
else viewController.view.tag == 2
{
destinationTextField.text = place.name
}
dismiss(animated: true, completion: nil)
}
I set the reference of clicked textField to selectedTextField in both my IBActions.
var selectedTextField = UITextField()
#IBAction func locationTextFieldTapped(_ sender: Any) {
selectedTextField = locationTextField
//locationTextField.resignFirstResponder()
let autoCompleteController = GMSAutocompleteViewController()
locationTextField.tag = 0
autoCompleteController.delegate = self
present(autoCompleteController, animated: true, completion: nil)
}
#IBAction func destinationTextField(_ sender: Any) {
selectedTextField = destinationTextField
//destinationTextField.resignFirstResponder()
let autoCompleteController = GMSAutocompleteViewController()
destinationTextField.tag = 1
autoCompleteController.delegate = self
present(autoCompleteController, animated: true, completion: nil)
}
Then in the GMSAutocompleteViewControllerDelegate
I did this:
self.selectedTextField.text = place.name
I just started programming with swift a few months ago and I am currently working on a few random projects to help me learn the language better. Currently, I am trying to create a UI similar to Snapchat's by using a UIPageViewController. The horizontal scroll is working and I can navigate through the pages correctly but I want to change the initial view controller that is presented. In my code below, I have three ViewControllers (vc1, vc2, vc3). I want to keep them in that same order but display vc2 first, so basically have it in the middle of the other two. All help is appreciated. Thanks!
import UIKit
import FirebaseDatabase
import FirebaseAuth
import Firebase
class RootPageViewController: UIPageViewController, UIPageViewControllerDataSource {
let Cards = CardsVC()
lazy var viewControllerList:[UIViewController] = {
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc1 = sb.instantiateViewController(withIdentifier: "MessagesVC")
let vc2 = sb.instantiateViewController(withIdentifier: "CardsVC")
let vc3 = sb.instantiateViewController(withIdentifier: "ProfileVC")
return[vc1, vc2, vc3]
}()
override func viewDidLoad() {
super.viewDidLoad()
//if Auth.auth().currentUser?.uid == nil{
//logout()
//}else{
self.dataSource = self
if let firstViewController = viewControllerList.first{
self.setViewControllers([firstViewController], direction: .forward, animated: true, completion: nil)
}
//}
}
func logout(){
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let verifyPhoneVC = storyboard.instantiateViewController(withIdentifier: "enterPhoneNumber")
present(verifyPhoneVC, animated: true, completion: nil)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let vcIndex = viewControllerList.index(of: viewController) else {return nil}
let previousIndex = vcIndex - 1
guard previousIndex >= 0 else {return nil}
guard viewControllerList.count > previousIndex else {return nil}
return viewControllerList[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let vcIndex = viewControllerList.index(of: viewController) else {return nil}
let nextIndex = vcIndex + 1
guard viewControllerList.count != nextIndex else {return nil}
guard viewControllerList.count > nextIndex else {return nil}
return viewControllerList[nextIndex]
}
}
You have the entire code ready, all that you need to do is pass the secondViewController from the viewControllerList in viewDidLoad thats all
So replace
if let firstViewController = viewControllerList.first{
self.setViewControllers([firstViewController], direction: .forward, animated: true, completion: nil)
}
with
let secondViewController = viewControllerList[1]
self.setViewControllers([secondViewController], direction: .forward, animated: true, completion: nil)
Hope this helps
Here I have written an extension in swift 3 for navigating next and previous page in UIPageViewController. You can modify as your requirement.
extension UIPageViewController {
func goToNextPage(animated: Bool = true) {
guard let currentViewController = self.viewControllers?.first else { return }
guard let nextViewController = dataSource?.pageViewController(self, viewControllerAfter: currentViewController) else { return }
setViewControllers([nextViewController], direction: .forward, animated: animated, completion: nil)
}
func goToPreviousPage(animated: Bool = true) {
guard let currentViewController = self.viewControllers?.first else { return }
guard let previousViewController = dataSource?.pageViewController(self, viewControllerBefore: currentViewController) else { return }
setViewControllers([previousViewController], direction: .reverse, animated: animated, completion: nil)
}
}
I hope you find it useful.
Open to suggestions.
Basically I'd like to move my PageViewController not only with swipe but also with buttons.
#IBAction func nextButtonAction(_ sender: Any){
}
My PageViewController looks like in this guide
What I have:
PageViewController
3 Viewcontrollers
Sorry if it's a duplicate didn't found exactly same situation
tried to call UIPageViewControllerDataSource on each ViewController
but didn't work also I think this is not the best approach
Edited:
This is my PageViewController
class PageVC: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
lazy var VCarr: [UIViewController] = {
return [self.VCInstance(name: "signUpVerification"),
self.VCInstance(name: "signUpVerification1"),
self.VCInstance(name: "signUpVerification2"),
]
}()
func VCInstance(name:String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier:name)
}
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.delegate = self
if let firsVC = VCarr.first {
setViewControllers([firsVC], direction: .forward, animated: true, completion: nil)
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = VCarr.index(of: viewController) else { return nil }
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return nil
}
return VCarr[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = VCarr.index(of: viewController) else { return nil }
let nextIndex = viewControllerIndex + 1
guard nextIndex < VCarr.count else {
return nil
}
return VCarr[nextIndex]
}
}
extension PageVC {
func goToNextPage(animated: Bool = true, completion: ((Bool) -> Void)? = nil) {
if let currentViewController = VCarr.first {
if let nextPage = dataSource?.pageViewController(self, viewControllerAfter: currentViewController) //for some reasons there's nil {
setViewControllers([nextPage], direction: .forward, animated: animated, completion: completion)
}
}
}
}
this is Viewcontroller where I call it:
var pageVC = PageVC()
#IBAction func nextButtonAction(_ sender: Any){
pageVC.goToNextPage()
}
Solution:
YourViewController: {
#IBAction func nextButtonAction(_ sender: Any){
let pageViewController = self.parent as! PageVC
pageViewController.nextPageWithIndex(index: 1)
}
}
PageViewController: {
func nextPageWithIndex(index: Int) {
setViewControllers([VCarr[index]], direction: .forward, animated: true, completion: nil)
}
}
To move page viewController to by button click you can achieve very simply by using following extension
extension UIPageViewController {
func goToNextPage(animated: Bool = true, completion: ((Bool) -> Void)? = nil) {
if let currentViewController = viewControllers?[0] {
if let nextPage = dataSource?.pageViewController(self, viewControllerAfter: currentViewController) {
setViewControllers([nextPage], direction: .forward, animated: animated, completion: completion)
}
}
}
func goToPreviousPage(animated: Bool = true, completion: ((Bool) -> Void)? = nil) {
if let currentViewController = viewControllers?[0] {
if let previousPage = dataSource?.pageViewController(self, viewControllerBefore: currentViewController){
setViewControllers([previousPage], direction: .reverse, animated: true, completion: completion)
}
}
}
}
To move forward
self.pageViewController.goToNextPage()
To move backward
self.pageViewController.goToPreviousPage()
don't forget to check whether the index available or not
for example:
if pageArray.count > currentIdex {
self.pageViewController.goToNextPage()
}
Hope this will help you
Try adding this method in your button Action for moving from first View Controller to second:
self.pageViewController.setViewControllers([self.viewControllerAtIndex(1)!], direction: .forward, animated: true, completion: nil)
pageControl2.currentPage = 1
Rest you can add your current PageTracker using PageControl.
self.pageViewController.setViewControllers([self.viewControllerAtIndex(pageControl.currentPage + 1)!], direction: .forward, animated: true, completion: nil)
pageControl.currentPage += 1
I'm trying to use UIPageViewController, and the slide is taken to account in debug, but the pages don't update:
import UIKit
class TutorialPageViewController: UIPageViewController {
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [UINib(nibName: "FinansicalTradingViewController", bundle:nil).instantiate(withOwner: self, options: nil)[0] as? FinansicalTradingViewController,
UINib(nibName: "VideoViewController", bundle:nil).instantiate(withOwner: self, options: nil)[0] as? VideoViewController,
UINib(nibName: "AllowZeroViewController", bundle:nil).instantiate(withOwner: self, options: nil)[0] as? AllowZeroViewController,
UINib(nibName: "TutorialStartViewController", bundle:nil).instantiate(withOwner: self, options: nil)[0] as? TutorialStartViewController]
}() as! [UIViewController]
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
// let vc = UINib(nibName: "VideoViewController", bundle:nil).instantiate(withOwner: self, options: nil)[0] as? VideoViewController
// setViewControllers([vc!], direction: .forward, animated: true, completion: nil)
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .forward,
animated: true,
completion: nil)
}
}
}
extension TutorialPageViewController: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController,
viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return nil
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
return orderedViewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController,
viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = orderedViewControllers.count
guard orderedViewControllersCount != nextIndex else {
return nil
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}