I have some issue with this animation :
fileprivate var rootKey: UITransitionContextViewControllerKey
{
return presenting ? .from : .to
}
fileprivate func root(from context: UIViewControllerContextTransitioning) -> ElongationViewController
{
let viewController = context.viewController(forKey: rootKey)
if let navi = viewController as? UINavigationController
{
for case let elongationViewController as ElongationViewController in navi.viewControllers
{
return elongationViewController
}
} else if let elongationViewController = viewController as? ElongationViewController
{
return elongationViewController
}
fatalError("Can't get `ElongationViewController` from UINavigationController nor from context's viewController itself.")
}
I always go to the fatalError except when my ViewController is the Initial View Controller.
I think it's because of .from in rotKey, how can i change .from to my actual ViewController ?
Related
I use the following to check for the top most view controller. I need to check if the top view controller is an ImagePickerController
guard let window = UIApplication.shared.windows.first(where: \.isKeyWindow) else { return }
guard let topVC = window.topViewController() else { return }
if topVC.isKind(of: ImagePickerController.self) {
// ...
}
but I get an error
How can I check if the top vc has/is an imagePicker presented?
extension UIWindow {
func topViewController() -> UIViewController? {
var top = self.rootViewController
while true {
if let presented = top?.presentedViewController {
top = presented
} else if let nav = top as? UINavigationController {
top = nav.visibleViewController
} else if let tab = top as? UITabBarController {
top = tab.selectedViewController
} else {
break
}
}
return top
}
}
You are setting ImagePickerController.self but the class name is UIImagePickerController
You can use like this
if let imagePicker = UIApplication.shared.windows.first?.topViewController() as? UIImagePickerController {
// Do your stuf
}
Or
if topVC.isKind(of: UIImagePickerController.self) {
// ...
}
Note: By using this you can not cast the top view controller as a UIImagePickerController. As it's designed by apple.
You can use this and access the view controller by this.
if let pickerHostClass = NSClassFromString("PUPhotoPickerHostViewController"), topVC.isKind(of: pickerHostClass) {
topVC.view.alpha = 0.5
}
In my case I have UITableView and have View all button for the listing of all the items in separate screens. So I added target for UIButton action method in cellForRowAt. Now what I am doing in action method:
#IBAction func btnViewAllOffer(_ sender: UIButton) {
let buttonPosition = sender.convert(CGPoint.zero, to: self.tblOfferView)
let indexPath = self.tblOfferView.indexPathForRow(at: buttonPosition)
if indexPath != nil {
if let type = self.homeData[indexPath!.section].type {
if type == HomeDataType.SponserProduct.rawValue {
let vc1 = self.storyboard?.instantiateViewController(withIdentifier: "ViewController1") as! ViewController1
if let title = self.homeData[indexPath!.section].title {
vc1.title = title
}
self.navigationController?.pushViewController(vc1, animated: true)
} else if type == HomeDataType.Offer.rawValue {
let vc2 = self.storyboard?.instantiateViewController(withIdentifier: "ViewController2") as! ViewController2
if let title = self.homeData[indexPath!.section].title {
vc2.title = title
}
self.navigationController?.pushViewController(vc2, animated: true)
} else if type == HomeDataType.BestSeller.rawValue {
let vc3 = self.storyboard?.instantiateViewController(withIdentifier: "ViewController3") as! ViewController3
if let title = self.homeData[indexPath!.section].title {
vc3.title = title
}
self.navigationController?.pushViewController(vc3, animated: true)
}
}
}
}
What I need, is there any way I can minimize the code and assign viewcontrollers dynamically so there is no need to instantiate each view controller and push them everytime?
Something like:
var vc = UIViewController()
if let type = self.homeData[indexPath!.section].type {
if type == HomeDataType.SponserProduct.rawValue {
vc = ViewController1()
}
else if type == HomeDataType.Offer.rawValue {
vc = ViewController2()
} else if type == HomeDataType.BestSeller.rawValue {
vc = ViewController3()
}
}
self.navigationController?.pushViewController(vc, animated: true)
Use a protocol (SimilarViewController) to define the common properties like title:
protocol SimilarViewController {
var title: String? { get set }
}
class ViewController1: UIViewController, SimilarViewController {
var title: String?
}
class ViewController2: UIViewController, SimilarViewController {
var title: String?
}
class ViewController3: UIViewController, SimilarViewController {
var title: String?
}
#IBAction func btnViewAllOffer(_ sender: UIButton) {
let buttonPosition = sender.convert(CGPoint.zero, to: self.tblOfferView)
let indexPath = self.tblOfferView.indexPathForRow(at: buttonPosition)
if indexPath != nil {
if let type = self.homeData[indexPath!.section].type {
var vcGeneric: SimilarViewController?
if type == HomeDataType.SponserProduct.rawValue {
vcGeneric = self.storyboard?.instantiateViewController(withIdentifier: "ViewController1") as! ViewController1
} else if type == HomeDataType.Offer.rawValue {
vcGeneric = self.storyboard?.instantiateViewController(withIdentifier: "ViewController2") as! ViewController2
} else if type == HomeDataType.BestSeller.rawValue {
vcGeneric = self.storyboard?.instantiateViewController(withIdentifier: "ViewController3") as! ViewController3
}
if let title = self.homeData[indexPath!.section].title {
vcGeneric?.title = title
}
if let vcGeneric = vcGeneric as? UIViewController {
self.navigationController?.pushViewController(vcGeneric, animated: true)
}
}
}
}
1: create a struct and assign the value to it.
struct TitleDetails {
static var title : String = ""
}
2: create an extension of viewController and use it to avoid code repetition.
extension UIViewController {
func pushVC(_ vcName : String) {
let vc = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: vcname)
self.navigationController?.pushViewController(vc, animated: true)
}
}
3: now you can call it directly as,
TitleDetails.title = yourTitleValue
self.pushVC("ViewController1")
and in your ViewDidLoad() method of your destination view controller,
self.title = TitleDetails.title
Create BaseViewController and derived other ViewController from BaseViewController
class BaseViewController: UIViewController {
var viewTitle = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func pushVC(_ vcName : String) {
let vc = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: vcName)
self.navigationController?.pushViewController(vc, animated: true)
}
}
And use below code on ViewController you need like:
#IBAction func btnViewAllOffer(_ sender: UIButton) {
let buttonPosition = sender.convert(CGPoint.zero, to: self.tblOfferView)
let indexPath = self.tblOfferView.indexPathForRow(at: buttonPosition)
if indexPath != nil {
if let type = self.homeData[indexPath!.section].type {
self.viewTitle = self.homeData[indexPath!.section].title
if type == HomeDataType.SponserProduct.rawValue {
self.pushVC("ViewController1")
} else if type == HomeDataType.Offer.rawValue {
self.pushVC("ViewController2")
} else if type == HomeDataType.BestSeller.rawValue {
self.pushVC("ViewController3")
}
}
}
}
I'd like to get the name of the current loaded ViewController as String.
I got this code which is working quite fine:
if let window = UIApplication.shared.delegate?.window {
var viewcontroller = window!.rootViewController
if(viewcontroller is UINavigationController){
viewcontroller = (viewcontroller as! UINavigationController).visibleViewController
}
print(String(describing: viewcontroller!))
}
But the result doesn't looks like this:
"MyViewController1"
it looks like this:
<myapplication.MyViewController1: 0x30141e210>
How can I just get the ViewControllers name/title? There must be some more effective method then just split the String between "." and ":".
EDIT: "vc!.nibName" will not work because I'm using the Storyboard!
EDIT 2: This is what the result should look like: "MyViewController1"
EDIT 3: print(viewcontroller?.title) is also not working. It's just returning "nil"
Any help would be very appreciated!
if let titleString = viewController.navigationItem.title {
print(titleString)
}
This function will return the name of the currentViewController:
/// Returns The Class Name Of A Given View Controller
///
/// - Parameter viewController: UIViewController
/// - Returns: String
func classNameFrom(_ viewController: UIViewController) -> String{
let currentViewControllerName = NSStringFromClass(viewController.classForCoder).components(separatedBy: ".").last!
print("Current View Controller = \(currentViewControllerName)")
return currentViewControllerName
}
Which you can call in ViewDidLoad etc:
print(classNameFrom(self))
Second Approach and perhaps a more robust solution:
extension UIViewController {
/// Returns The Top Most ViewController In The Navigation Controller
///
/// - Parameter base: UIViewController
/// - Returns: UIViewController
func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = base as? UINavigationController {
return topViewController(base: navigationController.visibleViewController)
}
if let tabBarController = base as? UITabBarController {
if let selected = tabBarController.selectedViewController {
return topViewController(base: selected)
}
}
if let presentedViewController = base?.presentedViewController {
return topViewController(base: presentedViewController)
}
return base
}
/// Returns The Class Name Of A Given View Controller
///
/// - Parameter viewController: UIViewController
/// - Returns: String
func classNameFrom(_ viewController: UIViewController) -> String{
let currentViewControllerName = NSStringFromClass(viewController.classForCoder).components(separatedBy: ".").last!
print("Current View Controller = \(currentViewControllerName)")
return currentViewControllerName
}
}
Which can be called like so:
guard let thisViewController = topViewController(base: self) else { return }
print(classNameFrom(thisViewController))
It looks like nobody understood my question correctly. That's why I now use this "noob" solution:
if let window = UIApplication.shared.delegate?.window {
var viewcontroller = window!.rootViewController
if(viewcontroller is UINavigationController){
viewcontroller = (viewcontroller as! UINavigationController).visibleViewController
}
let viewControllerString = String(describing: viewcontroller!).split(separator: ".")[1].split(separator: ":")[0]
print(viewControllerString)
}
I hope that somebody can answer this question correctly, as long as we will probably have to live with this code lines!
I have an object from Realm and i assign each control to object, what i want is to pass each object that i assign on each childVC to the next childVC.
I have the object initialized on my UIPageViewcontroller:
var artist = Artist()
I use this function to scroll to nextViewController :
func scrollToViewController(index newIndex: Int) {
if let firstViewController = viewControllers?.first,
let currentIndex = orderedViewControllers.index(of: firstViewController) {
let direction: UIPageViewControllerNavigationDirection = newIndex >= currentIndex ? .forward : .reverse
let nextViewController = orderedViewControllers[newIndex]
scrollToViewController(viewController: nextViewController, direction: direction)
}
}
And i assign each textbox, label to this object on childVC FirstViewController:
artist.artistName = textField.text!
Same is on the next childVC SecondViewvController:
artist.genre = genres[0]
I want to pass each control value that i assign from previous VieController to the next ViewController, for example:
On my SecondViewController i want to have the previous value saved on artist.artistName and assign the value that i get from label. For example just to print the previous value on viewDidload.
class SecondViewController {
var artistNameFromFirstVC: String?
override func viewDidLoad() {
super.viewDidLoad()
print("ArtisName: \(artistNameFromFirstVC)")
// Setup Views
setupCollectionView()
}
...
}
I have more than 2 childControllers, so i want to pass each previous values to next controllers.
I appreciate any help. Thank you :)
Helps?
struct Artist {
var name: String
var genre: String
}
class FirstViewController: UIViewController {
var name:String?
}
class SecondViewController: UIViewController {
var genre:String?
}
enum ScrollController: Int {
case first = 0
case second
init(with index: Int) {
self = ScrollController(rawValue: index) ?? .first
}
var controller: (Artist) -> UIViewController {
return { artist in
switch self {
case .first:
let controller = FirstViewController()
controller.name = artist.name
return controller
case .second:
let controller = SecondViewController()
controller.genre = artist.genre
return controller
}
}
}
}
func scrollToViewController(index newIndex: Int) {
if let firstViewController = viewControllers?.first,
let currentIndex = orderedViewControllers.index(of: firstViewController) {
let direction: UIPageViewControllerNavigationDirection = newIndex >= currentIndex ? .forward : .reverse
let nextViewController = ScrollController(with: newIndex).controller
scrollToViewController(viewController: nextViewController, direction: direction)
}
}
I would like to traverse the view controller hierarchy in Swift and find a particular class. Here is the code:
extension UIViewController{
func traverseAndFindClass<T : UIViewController>() -> UIViewController?{
var parentController = self.parentViewController as? T?
^
|
|
// Error: Could not find a user-defined conversion from type 'UIViewController?' to type 'UIViewController'
while(parentController != nil){
parentController = parentController!.parentViewController
}
return parentController
}
}
Now, I know that the parentViewController property returns an optional UIViewController, but I do not know how in the name of God I can make the Generic an optional type. Maybe use a where clause of some kind ?
Your method should return T? instead of UIViewController?, so that the generic type
can be inferred from the context. Checking for the wanted class has also to
be done inside the loop, not only once before the loop.
This should work:
extension UIViewController {
func traverseAndFindClass<T : UIViewController>() -> T? {
var currentVC = self
while let parentVC = currentVC.parentViewController {
if let result = parentVC as? T {
return result
}
currentVC = parentVC
}
return nil
}
}
Example usage:
if let vc = self.traverseAndFindClass() as SpecialViewController? {
// ....
}
Update: The above method does not work as expected (at least not in the Debug
configuration) and I have posted the problem
as a separate question: Optional binding succeeds if it shouldn't. One possible workaround (from an answer to that question)
seems to be to replace
if let result = parentVC as? T { ...
with
if let result = parentVC as Any as? T { ...
or to remove the type constraint in the method definition:
func traverseAndFindClass<T>() -> T? {
Update 2: The problem has been fixed with Xcode 7, the
traverseAndFindClass() method now works correctly.
Swift 4 update:
extension UIViewController {
func traverseAndFindClass<T : UIViewController>() -> T? {
var currentVC = self
while let parentVC = currentVC.parent {
if let result = parentVC as? T {
return result
}
currentVC = parentVC
}
return nil
}
}
One liner solution (using recursion), Swift 4.1+:
extension UIViewController {
func findParentController<T: UIViewController>() -> T? {
return self is T ? self as? T : self.parent?.findParentController() as T?
}
}
Example usage:
if let vc = self.findParentController() as SpecialViewController? {
// ....
}
Instead of while loops, we could use recursion (please note that none of the following code is thoroughly tested):
// Swift 2.3
public extension UIViewController {
public var topViewController: UIViewController {
let o = topPresentedViewController
return o.childViewControllers.last?.topViewController ?? o
}
public var topPresentedViewController: UIViewController {
return presentedViewController?.topPresentedViewController ?? self
}
}
On the more general issue of traversing the view controller hierarchy, a possible approach is to have two dedicated sequences, so that we can:
for ancestor in vc.ancestors {
//...
}
or:
for descendant in vc.descendants {
//...
}
where:
public extension UIViewController {
public var ancestors: UIViewControllerAncestors {
return UIViewControllerAncestors(of: self)
}
public var descendants: UIViewControllerDescendants {
return UIViewControllerDescendants(of: self)
}
}
Implementing ancestor sequence:
public struct UIViewControllerAncestors: GeneratorType, SequenceType {
private weak var vc: UIViewController?
public mutating func next() -> UIViewController? {
guard let vc = vc?.parentViewController ?? vc?.presentingViewController else {
return nil
}
self.vc = vc
return vc
}
public init(of vc: UIViewController) {
self.vc = vc
}
}
Implementing descendant sequence:
public struct UIViewControllerDescendants: GeneratorType, SequenceType {
private weak var root: UIViewController?
private var index = -1
private var nextDescendant: (() -> UIViewController?)? // TODO: `Descendants?` when Swift allows recursive type definitions
public mutating func next() -> UIViewController? {
if let vc = nextDescendant?() {
return vc
}
guard let root = root else {
return nil
}
while index < root.childViewControllers.endIndex - 1 {
index += 1
let vc = root.childViewControllers[index]
var descendants = vc.descendants
nextDescendant = { return descendants.next() }
return vc
}
guard let vc = root.presentedViewController where root === vc.presentingViewController else {
return nil
}
self.root = nil
var descendants = vc.descendants
nextDescendant = { return descendants.next() }
return vc
}
public init(of vc: UIViewController) {
root = vc
}
}