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!
Related
I have a problem with big memory leaks when I use UIPageViewController. I have a view and I scroll it. I see that memory grows very fast.
i have an array of ViewController (about 350+ vc), the UIPageViewcontroller shows the every VC in a page, the problem is :
to append 350+ Viewcontroller in array is cause memory to grow up fast
so is there a way to append VC in array by viewController Before and viewController After method when scrolling ?
so the array will always be 2 index for example
arraypage1 = [vc1,vc2,vc3]
when scrolling to the next page arraypage1 well be [vc2,vc3,vc4]
so the index 0 remove but a new page will add at index 2
here is what i did so far :
at TestVC :
var TodayDate: String?
var data1 : String? , data2: String?
var data3: String? , data4: String?
var data5: String? , data6: String?
var imagee: UIImage?
at the UIpageViewcontroller :
let vc1 = self.storyboard?.instantiateViewController(withIdentifier: "page1")as! TestPageVC
vc1.TodayDate = "06.09.2022"
vc1.data1 = "welcome"
vc1.data2 = "The first data..."
vc1.data3 = "The first data..."
vc1.data4 = "The first data..."
vc1.data5 = "The first data..."
vc1.data6 = "The first data..."
vc1.imagee = UIImage(named: "0001")
let vc2 = self.storyboard?.instantiateViewController(withIdentifier: "page1")as! TestPageVC
vc2.TodayDate = "07.09.2022"
vc2.data1 = "welcome"
vc2.data2 = "The second data..."
vc2.data3 = "The second data..."
vc2.data4 = "The second data..."
vc2.data5 = "The second data..."
vc2.data6 = "The second data..."
vc2.imagee = UIImage(named: "0002")
let vc3 = self.storyboard?.instantiateViewController(withIdentifier: "page1")as! TestPageVC
vc3.TodayDate = "08.09.2022"
vc3.data1 = "welcome"
vc3.data2 = "The third data..."
vc3.data3 = "The third data..."
vc3.data4 = "The third data..."
vc3.data5 = "The third data..."
vc3.data6 = "The third data..."
vc3.imagee = UIImage(named: "0003")
let vc4 = self.storyboard?.instantiateViewController(withIdentifier: "page1")as! TestPageVC
vc4.TodayDate = "08.09.2022"
vc4.data1 = "welcome"
vc4.data2 = "The fourth data..."
vc4.data3 = "The fourth data..."
vc4.data4 = "The fourth data..."
vc4.data5 = "The fourth data..."
vc4.data6 = "The fourth data..."
vc4.imagee = UIImage(named: "0004")
let vc5 = self.storyboard?.instantiateViewController(withIdentifier: "page1")as! TestPageVC
vc3.TodayDate = "08.09.2022"
vc3.data1 = "welcome"
vc3.data2 = "The fifth data..."
vc3.data3 = "The fifth data..."
vc3.data4 = "The fifth data..."
vc3.data5 = "The fifth data..."
vc3.data6 = "The fifth data..."
vc3.imagee = UIImage(named: "0005")
arraypage1.append(contentsOf:[vc1, vc2, vc3])
// here i want add vc4 and remove vc1 when go to next page
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let currentIndex = arraypage2.firstIndex(of: viewController) else {
return nil
}
UserDefaults.standard.removeObject(forKey: "emptyView1")
let previousIndex = currentIndex - 1
guard previousIndex >= 0 else {
return nil
}
return arraypage2[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let currentIndex = arraypage2.firstIndex(of: viewController) else {
return nil
}
let afterIndex = currentIndex + 1
let afterIndex3 = currentIndex + 2
if afterIndex3 == arraypage2.count {
UserDefaults.standard.set("next", forKey: "emptyView1")
}
guard afterIndex < arraypage2.count else {
return nil
}
return arraypage2[afterIndex]
}
As you've seen, if you are using a UIPageViewController with many pages, you'll run into memory issues rather quickly if you keep an array of instantiated view controllers.
To avoid that, we want to dynamically instantiate the controllers when the Page View Controller requests them.
Here's a quick example...
First, we'll define a data structure (you'll likely have more properties):
struct MyPageData {
var title: String = "Title"
var subtitle: String = "Subtitle"
var imgName: String = "0.circle.fill"
var bkgColor: UIColor = .white
}
Next we'll lay out a "Page" view controller in Storyboard, so it looks something like this:
And set its Custom Class to TestPageVC and give it an Identifier of "page1":
class TestPageVC: UIViewController {
public var pageIndex: Int = 0
#IBOutlet var titleLabel: UILabel!
#IBOutlet var subtitleLabel: UILabel!
#IBOutlet var imageView: UIImageView!
func fillData(_ mpd: MyPageData) {
titleLabel.text = mpd.title
subtitleLabel.text = mpd.subtitle
if let img = UIImage(systemName: mpd.imgName) {
imageView.image = img
}
view.backgroundColor = mpd.bkgColor
}
}
Note that we added a pageIndex property, which we'll use to track the "pages" as they're displayed.
In our UIPageViewController, instead of an array of view controllers, we'll use an array of data structures:
// array of data -- NOT view controllers
var pageData: [MyPageData] = []
and in viewDidLoad() we'll generate 350 of those:
class MyDynamicViewController: UIPageViewController {
// array of data -- NOT view controllers
var pageData: [MyPageData] = []
override func viewDidLoad() {
super.viewDidLoad()
let colors: [UIColor] = [
.red, .green, .blue,
.cyan, .magenta, .yellow,
.systemRed, .systemGreen, .systemYellow,
]
// let's create 350 "pages"
var mpd: MyPageData
for i in 0..<350 {
mpd = MyPageData()
mpd.title = "Page \(i)"
mpd.subtitle = "Subtitle for page \(i)"
mpd.imgName = "\(i % 20).circle.fill"
mpd.bkgColor = colors[i % colors.count]
pageData.append(mpd)
}
dataSource = self
delegate = nil
// instantiate the first "page"
guard let sb = storyboard,
let vc = sb.instantiateViewController(withIdentifier: "page1") as? TestPageVC
else {
fatalError("Could not instantiate view controller!")
}
vc.loadViewIfNeeded()
vc.fillData(pageData[0])
vc.pageIndex = 0
setViewControllers([vc], direction: .forward, animated: false, completion: nil)
}
}
As we see at the end of viewDidLoad(), we instantiated the first "page" view controller and called setViewControllers([vc], ...)
Now we write the UIPageViewControllerDataSource to instantiate and return new instances of "page" view controllers, rather than keeping them in a giant array:
// typical Page View Controller Data Source
extension MyDynamicViewController: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let vc = viewController as? TestPageVC else { return nil }
if vc.pageIndex == 0 { return nil }
guard let sb = storyboard,
let newVC = sb.instantiateViewController(withIdentifier: "page1") as? TestPageVC
else {
fatalError("Could not instantiate view controller!")
}
let n = vc.pageIndex - 1
newVC.loadViewIfNeeded()
newVC.fillData(pageData[n])
newVC.pageIndex = n
return newVC
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let vc = viewController as? TestPageVC else { return nil }
if vc.pageIndex >= pageData.count - 1 { return nil }
guard let sb = storyboard,
let newVC = sb.instantiateViewController(withIdentifier: "page1") as? TestPageVC
else {
fatalError("Could not instantiate view controller!")
}
let n = vc.pageIndex + 1
newVC.loadViewIfNeeded()
newVC.fillData(pageData[n])
newVC.pageIndex = n
return newVC
}
}
// typical Page View Controller Delegate
extension MyDynamicViewController: UIPageViewControllerDelegate {
// if you do NOT want the built-in PageControl (the "dots"), comment-out these funcs
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return pageData.count
}
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
guard let firstVC = pageViewController.viewControllers?.first as? TestPageVC else {
return 0
}
return firstVC.pageIndex
}
}
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
}
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 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 ?
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
}
}