swift indexOf for [AnyObject] array - ios

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)
}

Related

Find a uiviewcontroller from uinavigationcontroller

I am trying to figure out a generic way to find a uiviewcontroller which is auto type casted. Currently, I do have this.
extension UINavigationController {
func contoller(ofType type:AnyClass) -> UIViewController? {
for controller in self.viewControllers {
if controller.isKind(of: type) {
return controller
}
}
return nil
}
}
Calling will be like:
if let controller = self.navigationController?.contoller(ofType: MyController.self) as? MyController{}
That's how I am able to get controller object and I need to type cast it also.
I am trying to figure out a way to do this like as:
if let controller:MyController = self.navigationController?.contoller(ofType: MyController.self){}
So that I will not need to do any type casting.
For this, I may need to do some changes in UINavigationController extension function.
Need some suggestion for this.
Use Generics:
extension UINavigationController {
func controller<T: UIViewController>(ofType _: T.Type) -> UIViewController? {
for controller in viewControllers where controller is T {
return controller
}
return nil
}
}
EDIT 1: I would return also the VC as type we requested.
And recommend to use first in the name, cause there might be more than one. And you might want to introduce lastController(as:) in the future
extension UINavigationController {
func firstController<T: UIViewController>(as _: T.Type) -> T? {
for case let controller as T in viewControllers {
return controller
}
return nil
}
}
Then usage could be:
let nav = UINavigationController()
nav.viewControllers = [UIViewController(), UITabBarController()]
let tabVC = nav.firstController(as: UITabBarController.self)
EDIT 2: You can shorten the extension body:
extension UINavigationController {
func firstController<T: UIViewController>(as _: T.Type) -> T? {
viewControllers.first(where: { $0 is T }) as? T
}
}

how to unit test UIApplication extension

Suppose I use this code that extracts the top most viewController
import UIKit
extension UIApplication {
class func topViewController(_ base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = base as? UINavigationController, navigationController.viewControllers.count > 0 {
return topViewController(navigationController.visibleViewController)
}
if let tabBarController = base as? UITabBarController {
if let selected = tabBarController.selectedViewController {
return topViewController(selected)
}
}
if let presentedViewController = base?.presentedViewController {
return topViewController(presentedViewController)
}
return base
}
}
How do I facilitate unit testing of this code? I would need to use an instance of UIApplication.shared. Any tips would be appreciated.
If instead this was an extension to UIViewController, you could omit the parameter (base) altogether.
The call would then change to
let top = UIApplication.shared.keyWindow?.rootViewController.topViewController()
In order to unit test this, we can simply create a ViewController and perform our tests.

Storing UIViewController generic with a protocol as a property

Okay, so I'm pretty sure I'm overthinking this.
I am passing through a viewController that conforms to a protocol as a generic like so:
static func sortPage<T: UIViewController>(controller: T, err: NSError) where T: SortAlertDelegate { }
What I want to be able to do is store that controlleras a property so I can access all the functions UIViewController gives me and the functions thats the SortAlerDelegate gives me.
Any ideas?
You can't specify a type and protocol conformance for a property. You'll need to cast your property to the correct type whenever you want to use specific features. However, you can make this less painful with a bit of boilerplate:
let myProperty: UIViewController? = nil {
willSet(newValue) {
if (newValue as? SortAlertDelegate != nil) {
myProperty = newValue
} else {
myProperty = nil
}
}
}
This way, if you try to set the property to an object which doesn't conform to the protocol, the set will be aborted and the property will be set to nil.
You can also write read-only properties in order to get your property as the type you need at the moment:
let myPropertyAsViewController: UIViewController? {
get { return myProperty }
}
let myPropertyAsDelegate: SortAlertDelegate? {
get {
if let myProperty = myProperty {
return myProperty as! SortAlertDelegate
} else {
return nil
}
}
}
In general it is not possible unless you move the generic constraint on top of your class definition like:
class ViewController<T: UIViewController> : UIViewController where T: SortAlertDelegate {
let delegateController: T
}
But you can also make two references in your class like
class ViewController: UIViewController {
let controller: UIViewController
let delegate: SearchAlertDelegate
}
And then store the same object as two different references.

How to randomize page order

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

How to compare UIViewController in Swift 3?

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)
}
}
}

Resources