Don't have to cast segue destination view controller anymore? - ios

I saw this in a codebase:
BlablaViewController *bbVC = segue.destinationViewController;
I thought this always needed to be casted to the correct type. Did something change in Objective-C recently that makes this cast no longer necessary?

since xcode 7 you have a new annotation called __kindof which allows you to point with UIViewController subclass (BlablaViewController in your case) to UIViewController. please see this example:
https://happyteamlabs.com/blog/how-to-use-__kindof-in-objective-c/

Related

Custom UITabBar subclass without Interface Builder

Using IB (be it with a Storyboard or a XIB), one can easily have the UITabBarController use a subclass of UITabBar, by editing the class name in the Identity Inspector -> Custom Class.
How to mimic this "custom class" feature of IB, without using it at all ?
I tried the following (in my subclass of UITabBarController) :
var customTabBar = MyCustomTabBarSubclass()
override var tabBar: UITabBar {
return customTabBar
}
To no avail – the tab bar is displayed, but blank. The issue is not elsewhere since returning super.tabBar from the overriden var fixes it.
The issue, I guess, is that I'm not setting up my customTabBar (frame, position, adding it to the view hierarchy), but I'd like to have the UITabBarController loadView (I think that's the one, not sure) do it for me, the same way it sets up any other UITabBar.
Ideas ?
I think you can't. For this question, I had some time to scan through the documentations of UITabBar and UITabBarController.
The tabBar property of UITabBarController is a get-only.
#available(iOS 3.0, *)
open var tabBar: UITabBar { get } // Provided for -[UIActionSheet showFromTabBar:]. Attempting to modify the contents of the tab bar directly will throw an exception.
Furthermore, it is stated in UITabBarController's documentation that you shouldn't manipulate such property.
You should never attempt to manipulate the UITabBar object itself
stored in this property. If you attempt to do so, the tab bar view
throws an exception. To configure the items for your tab bar
interface, you should instead assign one or more custom view
controllers to the viewControllers property. The tab bar collects the
needed tab bar items from the view controllers you specify.
The tab bar view provided by this property is only for situations
where you want to display an action sheet using the show(from:) method
of the UIActionSheet class.
Just wanna add: but unlike this UITabBarController, subclassing the UINavigationController gives you the power to init such subclass with a subclass of UINavigationBar:
UINavigationController(navigationBarClass: <#T##AnyClass?#>, toolbarClass: <#T##AnyClass?#>)
Unfortunately, UITabBarController does not have such kind of init method.
This works, but there's a chance you might not pass App Store review because it's setting a value that public API doesn't allow you to set.
Inside your UITabBarController's subclass' initWithNibName:bundle: or viewDidLoad, add this:
MyCustomTabBar *customTabBar = [[MyCustomTabBar alloc] initWithFrame:CGRectZero];
customTabBar.delegate = self;
[self setValue:customTabBar forKey:#"tabBar"];
Consider this just a proof of concept, not something you should necessarily use in your production app because it's technically using a private setTabBar: method.
If you choose to disregard what Apple says about what's supported (using a custom UITabBar subclass with Interface Builder, and only with it), here's a dirty solution (that works) :
It requires mild knowledge of the ObjC runtime, because we're going to swizzle stuff around... Essentially, the issue is that I can't force UITabBarController to instantiate the class I want it to instantiate (here, MyCustomTabBarSubclass). Instead, it always instantiates UITabBar.
But I know how it instantiates it : by calling -[[UITabBar alloc] initWithFrame:]. And I also know that all functions belonging to the init family are allowed to return either an instance of their class, or of a subclass (that's the basis of Class Clusters).
So, I'm going to use this. I'm going to swizzle (= replace the implementation) of UITabBar's -initWithFrame: method with my custom version of it, that, instead of calling up (self = [super initWithFrame:]) will call "down" (self = [MyCustomTabBarSubclass.alloc initWithFrame:]). Thus, the returned object will be of class MyCustomTabBarSubclass, which is what I'm trying to achieve.
Note how I'm calling MyCustomTabBarSubclass.alloc – this is because my subclass potentially has ivars that UITabBar does not have, thus making it larger in its memory layout. I might have to release self before reallocating it, otherwise I could be leaking the allocated memory, but I'm not sure at all (and ARC "forbids" me to do call -release, so I'd have to use another step of trickery to call it).
EDIT
(First thing, this method would also work for any case where IB's custom classes are of use).
Also note that implementing this requires writing ObjC code, as Swift does not allow us to call alloc, for instance – no pun intended. Here's the code :
IMP originalImp = NULL;
id __Swizzle_InitWithFrame(id self, SEL _cmd, CGRect frame)
{
Class c = NSClassFromString(#"MyBundleName.MyCustomTabBarSubclass");
self = [c alloc]; //so that we'll return an instance of MyCustomTabBarSubclass
if (self) {
id (*castedImp)(id, SEL, CGRect) = (id (*)(id, SEL, CGRect))originalImp;
self = castedImp(self, _cmd, frame); //-[super initWithFrame:]
}
return self;
}
You'll also have to ensure that the actual swizzling operation is only performed once (such as, dispatch_once). Here's the code that actually swizzles :
Method method = class_getInstanceMethod(NSClassFromString(#"UITabBar"), #selector(initWithFrame:));
IMP swizzleImp = (IMP)__Swizzle_InitWithFrame;
originalImp = method_setImplementation(method, swizzleImp);
So that's it for the ObjC side.
Swift-side :
#objc class MyCustomTabBarSubclass: UITabBar {
lazy var anIvar: Int = 0 //just a example obviously
// don't forget to make all your ivars lazy or optional
// because the initialisers WILL NOT BE CALLED, as we are
// circumventing the Swift runtime normal init mechanism
}
And before you initialise your UITabBarController, don't forget to call the ObjC code that performs the swizzling.
That's it ! You have cheated UITabBarController into instantiating your own subclass of UITabBar, and not the vanilla one. If you're working in pure ObjC, things are even easier (no messing with bridging headers, a subject I didn't cover here).
Obligatory DISCLAIMER : Messing with the ObjectiveC runtime is obviously not something to do lightly. Ensure you have no better solution – IMHO, using a XIB only for the purpose of avoiding such tinkering is a better idea than implementing my suggestion.
A example of issue that could arise : if you're using multiple tab bars in your app, you might not want all of them to be MyCustomTabBarSubclass instances. Using my code above without modifications would result in all tab bars to be instances of MyCustomTabBarSubclass, so you'd have to find a way to tell __Swizzle_InitWithFrame directly call the original implementation, or not.

why does instantiateViewControllerWithIdentifier need to use "as!"

let someVC = self.storyboard!.instantiateViewControllerWithIdentifier("something") as! SomethingViewController
why do we need to use "as! DataEntryViewController"
it works when I take it out using xcode 7.3.1
Define "works".
Yes, you can instantiate a view controller without caring which subclass of UIViewController it is. And the object you get back from that method will be a view controller, so it's safe to do things with it that can be done to all view controllers: present it modally, push it onto a navigation controller, add it to a tab controller, etc.
However, if you're going to do something with it that's specific to your view controller class -- like if SomethingViewController defines a property that lets you choose which Something it displays, and you want to assign to that property -- you need to do two things:
Test at runtime that the object you got back from instantiateViewControllerWithIdentifier is the class you expect it to be. (Because depending on the identifier you pass it could be some other class, or nothing at all if that identifier isn't in the storyboard.)
Let the compiler know that the variable you've assigned that object to is typed for that class, so that the compiler will let you access properties and call methods of that class.
Assignment with an as? or as! cast does both of those things in one step. (Whether to use as? or as! just depends on how much you trust yourself to make sure your storyboard identifiers are what they claim to be, and how you want to handle failure of such assumptions.)
In fact, even if you're not using properties or methods specific to that view controller class, that as! cast adds a runtime check that your view controller class is what you expect it to be. So the fact that nothing breaks when you take the cast out isn't a sign that the cast is superfluous — it's a sign that whatever breakage the cast was checking for is not currently happening.
That is, the line you quoted would lead to a crash if somebody changed your storyboard so that the identifier "something" was on a SomethingElseViewController. If you took that cast out, you wouldn't crash there. (And you'd probably run into trouble later.)
However, if what you really want to do is assert the validity of your storyboard and program, it might be better to be clear about that:
let someVC = self.storyboard!.instantiateViewControllerWithIdentifier("something")
assert(someVC is SomethingViewController)
// then do something non-SomethingViewController-specific with it

Xcode 7 Swift 2 impossible to instantiate UIViewController subclass of generic UITableViewController

I have a generic class:
class PaginatedTableViewController
<GenericElement, Source: PaginationDataSource
where Source.PaginatedGenericElement == GenericElement>:
UITableViewController
and another that I try to instantiate from storyboard:
class CandidatesTableViewController:
PaginatedTableViewController<Match, MatchPaginationDataSource>
I can't find CandidatesTableViewController in the storyboard Custom Class dropdown menu. If I force it then cast my controller in code, app crashes at runtime complaining my controller (that should be a CandidatesTableViewController instance) is in fact a UITableViewController instance.
Unknown class _TtC21MyProjectName29CandidatesTableViewController in
Interface Builder file.
Could not cast value of type
'UITableViewController' (0x1040917f8) to
'MyProjectName.CandidatesTableViewController' (0x1013a9890).
In my project this controller is embedded in another one that's why I cast it :
tableViewController = (segue.destinationViewController as! CandidatesTableViewController)
Does any one knows how to resolve this issue ?
Unfortunately, generic Swift classes are not visible to Objective-C code and also are not supported in Interface Builder (in storyboards and xibs). I find these two points closely related.
As a solution I would suggest you to use aggregation: do not make you view controller generic, but extract some logic to another (generic) class and use it inside your view controller.
It is possible if you manually load your generic VC into Objective-C runtime manually via the load() method i.e. call. PaginatedTableViewController.load() in your app delegate's init method. Idea from https://stackoverflow.com/a/43896830/671580

ios I want to access an object in MainViewController from AppDelegate

I used this line from the sample project AppPrefs:
UITableView *tableView = ((UITableViewController *)self.navController.visibleViewController).tableView;
and it works of course perfectly,
so I tried:
UISearchBar *searchBar = (UIViewController *)mySearchBar;
but I get warning that mySearchBar is undeclared, even it is a property and initialized in MainViewController (which is of class UIViewController).
I need to access various objects in MainViewController from AppDelegate, so the app could refresh itself whenever the settings is changed.
Ouch, your code is wrong at as much places as possible. Let me explain where:
One. You're casting mySearchBar to UIViewController * then assign it to a different pointer type (of type UISearchBar *).
Two. If you're writing this from the app delegate, how come the compiler should know which class' (even further, which object's) property are you using? There may be multiple objects/classes with a property of the same name.
I feel you're confusing typecasting and accessing properties (so I strongly recommend learning plain C correctly before digging straight into iOS development as this is such a basic question that you will likely produce low-quality code if you don't yet understand this). You should maintain an instance of your view controller (either make it a global variable, a singleton, or even better, a property of the application delegate object) and acces it from there. Example:
UISearchBar *bar = [[[[UIApplication sharedApplication] delegate] mainViewController] searchBar];
If "mySearchBar" its a property declared in your MainViewController, and your self.navController.visibleController is an instance of MainViewController. Just access it like this:
UISearchBar* searchBar= self.navController.visibleController.mySearchBar
Now you are trying to assign something that doesn't even exist in your AppDelegate, because mySearchBar has not been declared in your AppDelegate.

Clarification around UIViewController<Delegate> *_variable; declaration in iAdSuite tabbed example (iOS)

From Apples iADSuite tabbed example there is a variable defined with delegate.
UIViewController<BannerViewContainer> *_currentController;
later it's cast as such
_currentController = (UIViewController<BannerViewContainer> *)_tabBarController.selectedViewController;
Whats the significance of using "BannerViewContainer" in the declaration, how it relates to the later cast and what's happening under the covers here?
Regards
Jim
There's nothing to do with delegates here. BannerViewContainer is a protocol. (You might be confused because delegation is often defined via protocols.)
Declaring a variable or parameter with an angle-bracketed protocol name means that anything assigned to it must be an object which conforms to that protocol: if you try to pass an instance of UIViewController or some subclass thereof, you'll get a compiler warning unless that instance is of a UIViewController subclass which declares conformance to the BannerViewContainer protocol. (That is, you can pass an instance of FooViewController if its header file reads #interface FooViewController : UIViewController <BannerViewContainer>.)
The cast you see later follows the same pattern as many casts: it's a case where the programmer knows that the object he's assigning meets the requirements for that variable, but the reference he's using doesn't have a matching declaration. That is, the tab bar controller only knows that its selected view controller is a UIViewController (or any subclass thereof), but the programmer knows that the views he put into the tab bar are all UIViewController subclasses conforming to the BannerViewContainer protocol.

Resources