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
}
Related
I have a custom view that has some subviews like labels and text fields. When I use multiple custom views into one controller I want to know the subview Accessibility Identifier. What I want to achieve is that set parent identifier(parent_accessibility_identifier) and than subview identifier can be an extension of it (eg parent_accessibility_identifier_label, parent_accessibility_identifier_text_field). Can we do this by setting the parent identifier accessibly to false and adding labels and text into the child's view but is there any better way to do it? this code doesn't work in the subview class.
public override var accessibilityIdentifier: String? {
didSet {
if let accessibilityIdentifier = self.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier + "_label"
}
}
}
this work in the custom view class
public override var accessibilityIdentifier: String? {
didSet {
guard let accessibilityIdentifier = accessibilityIdentifier else { return }
textLabel.accessibilityIdentifier = accessibilityIdentifier + "_text_label"
}
}
I would suggest using swizzling for that. It enables you to override the behavior of opened properties and functions of system-level frameworks.
First, put the swizzling code somewhere:
///
extension UIView {
/**
*/
class func swizzle() {
let orginalSelectors = [
#selector(getter: accessibilityIdentifier),
]
let swizzledSelectors = [
#selector(getter: swizzledAccessibilityIdentifier)
]
let orginalMethods = orginalSelectors.map { class_getInstanceMethod(UIView.self, $0) }
let swizzledMethods = swizzledSelectors.map { class_getInstanceMethod(UIView.self, $0) }
orginalSelectors.enumerated().forEach { item in
let didAddMethod = class_addMethod(
UIView.self,
item.element,
method_getImplementation(swizzledMethods[item.offset]!),
method_getTypeEncoding(swizzledMethods[item.offset]!))
if didAddMethod {
class_replaceMethod(
UIView.self,
swizzledSelectors[item.offset],
method_getImplementation(orginalMethods[item.offset]!),
method_getTypeEncoding(orginalMethods[item.offset]!))
} else {
method_exchangeImplementations(orginalMethods[item.offset]!, swizzledMethods[item.offset]!)
}
}
}
/// that's where you override `accessibilityIdentifier` getter for
#objc var swizzledAccessibilityIdentifier: String {
if let parentIdentifier = superview?.accessibilityIdentifier {
return "\(parentIdentifier)_\(self.swizzledAccessibilityIdentifier)"
} else {
return self.swizzledAccessibilityIdentifier
}
}
}
Call UIView.swizzle(). Usually, you do it at the app launch. Somewhere in the AppDelegate or similar.
Setup view hierarchy, assign identifiers and test:
class ParentView: UIView {}
class SubclassedChildView: UIView {}
let parentView = ParentView()
let child1 = SubclassedChildView()
let child2 = UIView()
parentView.accessibilityIdentifier = "parent"
child1.accessibilityIdentifier = "child1"
child2.accessibilityIdentifier = "child2"
parentView.addSubview(child1)
child1.addSubview(child2)
print(parentView.accessibilityIdentifier) // "parent"
print(child1.accessibilityIdentifier) // "parent_child1"
print(child2.accessibilityIdentifier) // "parent_child1_child2"
I wanted to display 6 icons on a tab bar in a specific order. For that in the storyboard I have dragged a tab-controller and added 6 viewcontrollers to it. I also added an image to each of the tab bars and also made different viewcontrollers for each of the tab bars.
But the issue is the images/viewcontrollers aren't displaying as per the order specified in the storyboard.
I have a .swift file also for the tabbarcontroller. It has some code for configuring the tab bars given like so...
class SampleTabBarController: UITabBarController, UIGestureRecognizerDelegate {
// MARK: - Shared Instance
static var shared: SampleTabBarController?
// MARK: - Public Properties
var homeTitle = "Home"
var connectionsTitle = "Connections"
var flightTitle = "Flights"
var messagesTitle = "Messages"
var companyProfileTitle = "Company Profile"
var myCalendarTitle = "My Calendar"
// MARK: - Private Properties
fileprivate var homeNavigation = "HomeNavigation"
fileprivate var home: HomeViewController? = nil
fileprivate var homeIndex = 0
fileprivate var connectionsNavigation = "ConnectionsNavigation"
fileprivate var connections: ConnectionsViewController? = nil
fileprivate var connectionsIndex = 1
fileprivate var flightsNavigation = "FlightsNavigation"
fileprivate var flights: FlightsViewController? = nil
fileprivate var flightsIndex = 2
fileprivate var messagesNavigation = "MessagesNavigation"
fileprivate var messages: MessagesViewController? = nil
fileprivate var messagesIndex = 3
fileprivate var companyProfileNavigation = "CompanyProfileNavigation"
fileprivate var compProfile: CompanyProfileViewController? = nil
fileprivate var compProfileIndex = 4
fileprivate var myCalendarNavigation = "MyCalendarNavigation"
fileprivate var myCalendar: MyCalendarViewController? = nil
fileprivate var myCalendarIndex = 5
fileprivate var previousIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
SampleTabBarController.shared = self
initialConfiguration()
}
}
extension SampleTabBarController {
private func initialConfiguration() {
self.tabBarController(SampleTabBarController.shared!,
didEndCustomizing: SampleTabBarController.shared!.viewControllers!,
changed: true)
configureViewControllers()
}
fileprivate func configureViewControllers() {
// Home
if let viewControllers = self.viewControllers {
let navigationControllers = viewControllers.filter { $0.restorationIdentifier == homeNavigation }
if let navigation = navigationControllers.first as? UINavigationController ,
let homeView = navigation.viewControllers[0] as? HomeViewController {
self.home = homeView
}
}
// Connections
if let viewControllers = self.viewControllers {
let navigationControllers = viewControllers.filter { $0.restorationIdentifier == connectionsNavigation }
if let navigation = navigationControllers.first as? UINavigationController ,
let connectionView = navigation.viewControllers[0] as? ConnectionsViewController {
self.connections = connectionView
}
}
// Flight
if let viewControllers = self.viewControllers {
let navigationControllers = viewControllers.filter { $0.restorationIdentifier == flightsNavigation }
if let navigation = navigationControllers.first as? UINavigationController ,
let flightView = navigation.viewControllers[0] as? FlightsViewController {
self.flights = flightView
}
}
// Messages
if let viewControllers = self.viewControllers {
let navigationControllers = viewControllers.filter { $0.restorationIdentifier == messagesNavigation }
if let navigation = navigationControllers.first as? UINavigationController ,
let messagesView = navigation.viewControllers[0] as? MessagesViewController {
self.messages = messagesView
}
}
// Company Profile
if let viewControllers = self.viewControllers {
let navigationControllers = viewControllers.filter { $0.restorationIdentifier == companyProfileNavigation }
if let navigation = navigationControllers.first as? UINavigationController ,
let companyProfileView = navigation.viewControllers[0] as? CompanyProfileViewController {
self.compProfile = companyProfileView
}
}
// My Calendar
if let viewControllers = self.viewControllers {
let navigationControllers = viewControllers.filter { $0.restorationIdentifier == myCalendarNavigation }
if let navigation = navigationControllers.first as? UINavigationController ,
let calendarView = navigation.viewControllers[0] as? MyCalendarViewController {
self.myCalendar = calendarView
}
}
}
}
// MARK: - UITabBarControllerDelegate
extension SampleTabBarController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if previousIndex != tabBarController.selectedIndex {
previousIndex = tabBarController.selectedIndex
var elementText = ""
var elementValue = ""
switch previousIndex {
case homeIndex:
elementText = homeTitle
elementValue = BottomBarItems.home.rawValue
case connectionsIndex:
elementText = connectionsTitle
elementValue = BottomBarItems.connections.rawValue
case flightsIndex:
elementText = flightTitle
elementValue = BottomBarItems.flight.rawValue
case messagesIndex:
elementText = messagesTitle
elementValue = BottomBarItems.messages.rawValue
case compProfileIndex:
elementText = companyProfileTitle
elementValue = BottomBarItems.companyProfile.rawValue
case myCalendarIndex:
elementText = myCalendarTitle
elementValue = BottomBarItems.myCalendar.rawValue
default:
break
}
}
}
func tabBarController(_ tabBarController: UITabBarController, didEndCustomizing viewControllers: [UIViewController], changed: Bool) {
for (index, viewcontroller) in viewControllers.enumerated() {
// Home
if viewcontroller.restorationIdentifier == homeNavigation {
homeIndex = index
}
// Connections
if viewcontroller.restorationIdentifier == connectionsNavigation {
connectionsIndex = index
}
// Flight
if viewcontroller.restorationIdentifier == flightsNavigation {
flightsIndex = index
}
// Messages
if viewcontroller.restorationIdentifier == messagesNavigation {
messagesIndex = index
}
// Company Profile
if viewcontroller.restorationIdentifier == companyProfileNavigation {
compProfileIndex = index
}
// My Calendar
if viewcontroller.restorationIdentifier == myCalendarNavigation {
myCalendarIndex = index
}
}
}
}
But this doesn't seem to work...what is it that I'm doing wrong...?
I'm using Swift 3 in Xcode 8.3.3.
I have 2 view controllers, A and B that inherit from a regular UIViewController like this:
class A: UIViewController {}
class B: UIViewController {}
These 2 view controllers can be presented by a base view controller in a UINavigationController.
I have a different view controller, Test, which will eventually be presented by either A or B, meaning somewhere at the end of the hierarchy like this:
Base -> A or B -> Other -> Other -> Test
Test will present it's data differently depending on which one it came from.
I have an extension on UIViewController that provides a way of checking this, and it works just fine when I hardcode it for A and B:
extension UIViewController {
var isFromA: Bool {
if let nc = parent as? UINavigationController {
return nc.viewControllers.filter({ ($0 as? A) != nil }).count > 0
} else {
if let _ = parent as? A {
return true
} else if parent != nil {
return parent!.isFromA
} else {
return false
}
}
}
var isFromB: Bool {
if let nc = parent as? UINavigationController {
return nc.viewControllers.filter({ ($0 as? B) != nil }).count > 0
} else {
if let _ = parent as? B {
return true
} else if parent != nil {
return parent!.isFromB
} else {
return false
}
}
}
}
In the Test lifecycle code, I can use it like this:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if isFromA {
// do something
} else if isFromB {
// do something else
}
}
So now I'm going to be adding a new view controller, C, which can also eventually present a Test. I don't want to just copy and paste the code again when I make the isFromC var. I want to make a helper function that takes an instance of a type of class to use in those as? checks. The new code would look something like this:
extension UIViewController {
var isFromA: Bool {
return isFrom(A)
}
var isFromB: Bool {
return isFrom(B)
}
var isFromC: Bool {
return isFrom(C)
}
fileprivate func isFrom(_ viewControllerClass: UIViewController) -> Bool {
if let nc = parent as? UINavigationController {
return nc.viewControllers.filter({ ($0 as? viewControllerClass) != nil }).count > 0
} else {
if let _ = parent as? viewControllerClass {
return true
} else if parent != nil {
return parent!.isFrom(viewControllerClass)
} else {
return false
}
}
}
}
This doesn't compile, and it's not quite right anyways, because I don't have an actual instance of A, B, or C to pass into this helper function.
So what's the best way to solve this? Also note that I'm open to suggestions on reworking the helper function code as well, as I'm not sure if it covers all the combinations of navigation controllers and such.
I figured out a way to pass a type as a parameter, after doing some searching. But the examples I was seeing didn't specifically cover my context (detecting if the currently presented view controller is downstream of another type of view controller in some way), so I decided to pose the question anyway, and provide my own answer. I'm still interested in any changes to the helper function, though.
But here's my solution:
extension UIViewController {
var isFromA: Bool {
return isFrom(viewControllerClassType: A.self)
}
var isFromB: Bool {
return isFrom(viewControllerClassType: B.self)
}
var isFromC: Bool {
return isFrom(viewControllerClassType: C.self)
}
fileprivate func isFrom(viewControllerClassType type: UIViewController.Type) -> Bool {
if let nc = parent as? UINavigationController {
return nc.viewControllers.filter({ $0.isKind(of: type) }).count > 0
} else {
if let _ = parent?.isKind(of: type) {
return true
} else if parent != nil {
return parent!.isFrom(viewControllerClassType: type)
} else {
return false
}
}
}
}
I don't know if using UIViewController.Type is the best way to go, but it does the job.
You can modify what you have just a bit to use a generic type in your function and call it instead of your computed vars.
extension UIViewController {
func isFrom<T>(viewControllerClass: T) -> Bool {
guard let parent = parent else { return false }
if let nav = parent as? UINavigationController {
return nav.viewControllers.filter({ type(of: $0).self is T }).count > 0
} else {
if parent is T {
return true
} else {
return parent.isFrom(viewControllerClass: viewControllerClass)
}
}
}
}
Then call it with
if vc.isFrom(viewControllerClass: ClassA.self) {
// do something
} else if vc.isFrom(viewControllerClass: ClassB.self) {
// do something
}
That at least removes the need to add a new var for every parent class type.
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 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 ?