I'm working on a quote app, I want the pages, except the first one, to be randomized. I don't want people opening the app to see the same initial quotes over and over again. If you could respond with edit to the code I would really appreciate it. Thank you in advance!
import UIKit
class ModelController: NSObject, UIPageViewControllerDataSource {
let pageData:NSArray = ["All quotes are from Kanye West, Enjoy!", "I refuse to accept other people's ideas of happiness for me. As if there's a 'one size fits all' standard for happiness","I am the greatest","I still think I'm the greatest.","They say you can rap about anything except for Jesus, that means guns, sex, lies, video tapes, but if I talk about God my record won't get played Huh?"]
override init() {
super.init()
}
func viewControllerAtIndex(_ index: Int, storyboard: UIStoryboard) -> DataViewController? {
// Return the data view controller for the given index.
if (self.pageData.count == 0) || (index >= self.pageData.count) {
return nil
}
// Create a new view controller and pass suitable data.
let dataViewController = storyboard.instantiateViewController(withIdentifier: "DataViewController") as! DataViewController
dataViewController.dataObject = self.pageData[index] as! String
return dataViewController
}
func indexOfViewController(_ viewController: DataViewController) -> Int {
// Return the index of the given data view controller.
// For simplicity, this implementation uses a static array of model objects and the view controller stores the model object; you can therefore use the model object to identify the index.
return pageData.index(of: viewController.dataObject)
}
// MARK: - Page View Controller Data Source
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! DataViewController)
if (index == 0) || (index == NSNotFound) {
return nil
}
index -= 1
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! DataViewController)
if index == NSNotFound {
return nil
}
index += 1
if index == self.pageData.count {
return nil
}
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
You can randomise your quote array and pass on the randomised array.
Checkout these link:
How do I shuffle an array in Swift? and
Shuffle array swift 3
Related
I've made a simple weather app as a side project (I'm not a software engineer) that shows temperatures in Fahrenheit and Celsius at the same time. The user can swipe horizontally between cities:
I've achieved this by using the multi-page app template that was available in Xcode as a new app template up until Xcode 9. When building the app up until Xcode 10, the problem was that when the user added a city to the array of cities, pages for newly added cities would not render if the user was on the last page of the carousel at the time of adding a new city. Even if so, when the user swiped left and then back right, the newly added cities would show up. Now, in Xcode 11, new view controllers for newly added cities never render, unless the user relaunches the app. That's a pretty big issue I'd like to fix.
I need help in figuring out how new view controllers can be rendered on demand into the existing multi-page carousel after addCity is called, even if the user is currently on the last page and w/o requiring an app restart.
Below is the full code that drives that multi-page carousel. Apologies some of the hacky stuff in there. For context, I'm a non-technical product manager trying to make some iOS apps as side projects.
import UIKit
import StoreKit
/*
A controller object that manages a simple model
The controller serves as the data source for the page view controller; it therefore implements pageViewController:viewControllerBeforeViewController: and pageViewController:viewControllerAfterViewController:.
It also implements a custom method, viewControllerAtIndex: which is useful in the implementation of the data source methods, and in the initial configuration of the application.
There is no need to actually create view controllers for each page in advance -- indeed doing so incurs unnecessary overhead. Given the data model, these methods create, configure, and return a new view controller on demand.
*/
class ModelController: NSObject, UIPageViewControllerDataSource {
var rootViewController = RootViewController()
var cities = [""]
let defaults = UserDefaults.standard
let currentCount = UserDefaults.standard.integer(forKey: "launchCount")
var pageViewController: UIPageViewController?
override init() {
super.init()
self.cities = self.defaults.stringArray(forKey: "SavedStringArray") ?? [String]()
if self.cities == [""] || self.cities.count == 0 {
self.cities = ["Current Location", "San Francisco", "New York"]
}
}
func addCity(name:String) {
self.cities.append(name)
self.defaults.set(self.cities, forKey: "SavedStringArray")
}
func viewControllerAtIndex(_ index: Int, storyboard: UIStoryboard) -> DataViewController? {
// Return the data view controller for the given index.
if (self.cities.count == 0) || (index >= self.cities.count) {
return nil
}
// Create a new view controller and pass suitable data.
let dataViewController = storyboard.instantiateViewController(withIdentifier: "DataViewController") as! DataViewController
//get city name
dataViewController.dataObject = self.cities[index]
return dataViewController
}
func indexOfViewController(_ viewController: DataViewController) -> Int {
// Return the index of the given data view controller.
// For simplicity, this implementation uses a static array of model objects and the view controller stores the model object; you can therefore use the model object to identify the index.
return self.cities.firstIndex(of: viewController.dataObject) ?? NSNotFound
}
// MARK: - Page View Controller Data Source
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! DataViewController)
if (index == 0) || (index == NSNotFound) {
return nil
}
index -= 1
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! DataViewController)
if index == NSNotFound {
return nil
}
index += 1
if index == self.cities.count {
return nil
}
if index > 1 && currentCount > 2 {
print ("request review")
SKStoreReviewController.requestReview()
}
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
}
I am trying to compare to UIViewController in Swift 3 but there is some error
extension UINavigationController
{
func myPopToViewController(viewController:UIViewController, animated:Bool) -> UIViewController? {
var arrViewControllers:[UIViewController] = []
arrViewControllers = self.viewControllers
for vc:UIViewController in arrViewControllers {
if(vc.isKind(of: viewController) ) // This Line gives me error
{
return (self.navigationController?.popToViewController(vc, animated: animated)?.last)!
}
}
return nil
}
}
/Users/varunnaharia/Documents/Projects/appname/appname/Public/UINavigationController+Extra.swift:18:30: Cannot convert value of type 'UIViewController' to expected argument type 'AnyClass' (aka 'AnyObject.Type')
and if try to use
if(vc is viewController)
It gives
/Users/varunnaharia/Documents/Projects/appname/appname/Public/UINavigationController+Extra.swift:18:22: Use of undeclared type 'viewController'
I am calling it through this
self.navigationController?.popOrPopToViewController(viewController: MyUIViewController(), animated: false)
for viewsController in arrViewControllers
{
if(viewsController.isKind(of: YourControllerClassName.self)){
}
}
Swift 4
Hope it will work for you
extension UINavigationController {
func myPopToViewController(viewController:UIViewController, animated:Bool) {
var arrViewControllers:[UIViewController] = []
arrViewControllers = self.viewControllers
for vc:UIViewController in arrViewControllers {
if(vc.isKind(of: viewController.classForCoder)){
(self.popToViewController(vc, animated: animated))
}
}
}
}
In swift, we use is instead of isKind(of:).
is is used to check the type of the object.
So you can use,
if(vc is UIViewController)
But I think here you are trying to match the 2 references of UIViewController.
So, you need to use === instead of is. This operator is used to match 2 references of same type.
if(vc === viewController)
If you want to compare to a particular view controller you have to compare their refererences.
Try this...
if(vc === viewController) )
{
return (self.navigationController?.popToViewController(vc, animated: animated)?.last)!
}
i just modify the answer of Mr. #BangOperator for move to particular View controller.
extension UINavigationController {
func popTo(controllerToPop:UIViewController) {
//1. get all View Controllers from Navigation Controller
let controllersArray = self.viewControllers
//2. check whether that view controller is exist in the Navigation Controller
let objContain: Bool = controllersArray.contains(where: { $0 == controllerToPop })
//3. if true then move to that particular controller
if objContain {
self.popToViewController(controllerToPop, animated: true)
}
}
}
I have followed the basic tutorial here to create a UIPageViewController that swipes through different ViewControllers, all of which I create on Main.Storyboard. I want to make an application that generates dynamic ViewControllers, meaning I will have to instantiate the ViewControllers in the UIPageViewController programatically.
However, I am trying to take everything a bit further. I am going to have a dynamic set of ViewControllers to display based on data I have in Firebase. On each swipe, I want to instantiate a view that is going to contain an image (the image will be stored on Amazon Web Services, and a link to the image will be stored in Firebase). I currently have this setup in my Android application and use Picasso to load the image in my fragment.
Since all of the data is going to be dynamic, I want to find the best way to:
-Instantiate new ViewControllers based on dynamic firebase data that changes each day
-Have each ViewController display the associated image I have stored in Firebase
All of the ViewControllers will have the same "skeleton," meaning each one will have an image accompanied by some text. Below is my current code, and I was hoping to receive some advice / assistance.
import UIKit
class PageViewTutorial: UIPageViewController {
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .Forward,
animated: true,
completion: nil)
}
}
//this will be dynamic based on firebase data
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [self.newColoredViewController("Green"),
self.newColoredViewController("Red"),
self.newColoredViewController("Blue")]
}()
private func newColoredViewController(color: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil) .
instantiateViewControllerWithIdentifier("\(color)ViewController")
}
}
// MARK: PageViewTutorialDataSource
extension PageViewTutorial: UIPageViewControllerDataSource {
func pageViewController(pageViewController: UIPageViewController,
viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.indexOf(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,
viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.indexOf(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]
}
}
After new comments I would do it this way:
Make some Poll object with variables text, images, id, day an so on.
Make an PollViewModel initialized with Poll object, that will be respond to data presentation
Make an UIViewController/UITableViewController subclass initialized with PollViewModel
Make an subclasses of UITableViewCell for every part of the poll UI, that can beand display it in UITableView
Make a PageViewTutorial class (or its own ViewModel) initialized with current array of Poll objects from FireBase
In UIPageViewControllerDataSource handle objects from array and return vc initialized with model initialized with with poll object. ;)
Trying to get the index of an array ([AnyObject]). What's the part that I'm missing?
extension PageViewController : UIPageViewControllerDelegate {
func pageViewController(pageViewController: UIPageViewController, willTransitionToViewControllers pendingViewControllers: [AnyObject]) {
let controller: AnyObject? = pendingViewControllers.first as AnyObject?
self.nextIndex = self.viewControllers.indexOf(controller) as Int?
}
}
I have tried with Swift 1.2 this approach:
func indexOf<U: Equatable>(object: U) -> Int? {
for (idx, objectToCompare) in enumerate(self) {
if let to = objectToCompare as? U {
if object == to {
return idx
}
}
}
return nil
}
We need to cast the object we're testing to a UIViewController, since we know that are array of controllers is holding UIViewControllers (and we know that UIViewControllers conform to Equatable.
extension PageViewController : UIPageViewControllerDelegate {
func pageViewController(pageViewController: UIPageViewController, willTransitionToViewControllers pendingViewControllers: [AnyObject]) {
if let controller = pendingViewControllers.first as? UIViewController {
self.nextIndex = self.viewControllers.indexOf(controller)
}
}
}
The logic behind the error is that in order for the indexOf method to compare the object you pass in, it must compare them using the == operator. The Equatable protocol specifies that the class has implemented this function, so this is what indexOf requires its arguments conform to.
Objective-C doesn't have this same requirement, but the actual Objective-C implementation ends up meaning that the argument is compared with objects in the array using the isEqual: method (which NSObject and therefore all Objective-C classes implement).
You have to cast the viewController property to an Array object:
if let controllers = self.viewControllers as? [UIViewController] {
self.nextIndex = controllers.indexOf(controller)
}
I don't understand how indexOfVC returns an index: the dataObject is an optional in the ContentViewController :
class ContentViewController : UIViewController {
var dataObject:AnyObject?
, and when we invoke indexOfVC, pageContent contains html strings. How the dataObject can be found in pageContent if dataObject is empty, and pageContent is filled with html text ?
func viewControllerAtIndex(index:Int)->ContentViewController?{
if (index < 0) || (index >= pageContent.count){
return nil
}
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
var vc = storyboard.instantiateViewControllerWithIdentifier("ContentViewController") as ContentViewController
vc.dataObject = pageContent[index]
return vc
}
/* HERE */
func indexOfVC(viewController:ContentViewController) ->Int {
if let dataObject : AnyObject = viewController.dataObject {
return pageContent.indexOfObject(dataObject)
}
else {
return NSNotFound
}
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
var index : Int = indexOfVC(viewController as ContentViewController)
println("after: index: \(index)") //found 0, then 1, 2,... when scrolling right
if (index == NSNotFound) {
return nil
}
index++
return viewControllerAtIndex(index)
}
//called in viewdidload
func createContentPages(){
var pageStrings = [String]()
for i in 1...11 {
pageStrings.append(String("<html><head></head><body><br><h1>Chapter \(i)</h1><p>This is the page \(i) of content displayed using UIPageViewController in iOS 8.</p></body></html>"))
}
pageContent = pageStrings
}
The code is from : http://www.techotopia.com/index.php/An_Example_Swift_iOS_8_UIPageViewController_Application
The answer is that dataObject is never nil at the time that indexOfVC is called. Although it is optional, and will be nil when the ContentViewController is first initialised, as soon as the viewControllerAtIndex method creates a ContentViewController, it sets its dataObject to one of the elements of the pageContent array:
var vc = storyboard.instantiateViewControllerWithIdentifier("ContentViewController") as ContentViewController
vc.dataObject = pageContent[index]
So the overall flow is:
The starting page is set in the viewDidLoad of the main view controller:
let startingViewController: ContentViewController = viewControllerAtIndex(0)!
This creates a VC and sets its dataObject to pageContent[0].
If you then swipe, the pageViewController will call viewControllerAfterViewControler:viewController. In this call, the viewController argument is the current view controller. The method needs to know the index of the current VC, so it calls indexOfVC with viewController as the argument.
indexOfVC reads the dataObject value for that view controller, which is pageContent[0]. Hence pageContent.indexOfObject(dataObject) returns the integer 0.
This is then incremented (index++) and so the call to viewControllerAtIndex will create a new VC for index 1. This will create a new view controller and immediately set its dataObject to pageContent[1]. And the cycle continues...
So as currently coded, indexOfVC is never passed a viewController where its dataObject is nil. But if it ever were, it would return the special value NSNotFound. Then viewControllerAfterViewController: checks the return value and, if it is NSNotFound, returns nil - which tells the page view controller not to permit the swipe.