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.
I have an object called PanelView in Cocoa Touch which has a UINavigationController instance variable. At some point in my app, when the UINavigationController is presented as modal view, I send in a PanelView object as a parameter to the rootViewController of the UINavigationController where it is stored as an instance variable. I declare it how I would normally in the .h file:
PanelView *_panelView;
#property(nonatomic, strong) PanelView *panelView;
And in the .m file:
#synthesize panelView = _panelView;
So far I've had no side effects, but I'm wondering if I'm declaring this correctly as it might be a circular reference. What is the correct way to declare this variable?
Objective-C has evolved to be pretty forgiving on syntax, especially on iOS. You don't actually need to declare the variable that's acting as the backing store for your #property -- it will be synthesized for you.
It used to be the case that if you declared both a getter and a setter, then you would need to insert the #synthesize in your #implementation block, as you have it above. I just tried it out in Xcode 7.1 to double-check, and the compiler didn't complain for either a Cocoa Touch project or a command-line app project.
It seems from your follow-up question that you need to sort out the ownership of your data. Circular references in and of themselves usually aren't the problem. Keeping strong circular references is. That's how you get memory cycles that lead to leaks.
I would advise that you change the UINavigationView's reference to be a weak reference, since it the PanelView is guaranteed to be in existence as long as the UINavigationView is in existence.
In iOS 7 I used to define weak reference to popover controller inside my view controller (which displays in popover). I used this reference to popover controller in order to dismiss popover programmatically. Also, I defined delegate of popover controller in order to track dismissing events (popoverControllerShouldDismissPopover etc).
In iOS 8 it stops working. Investigation shows that weak reference point to nil after some point. The delegate stops working as well (as I understand it's because delegate was defined in popover controller which got destroyed in iOS 8 for some reason after popover displays).
Problem was solved by changed property to be strong reference.
For some popovers (I have bunch of them) I had to add strong reference only for the reason to keep popoverController alive because I need the delegate to work. It's obvious hack. I added property which I don't really need nor use.
Could you please clarify if it's right approach. My concern is that strong reference may lead to memory leaks. Also I don't quite understand why popoverController get destroyed in iOS 8 while popover still on the screen.
This is my view controller with weak property. After changing weak to strong to start working well under iOS 8:
#interface TFDSuggestionViewController : UIViewController
...
#property (weak, nonatomic) UIPopoverController *myPopoverController;
...
#end
This is how I assign value to my property and delegate in prepareForSegue method in calling view controller:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"suggestions"]) {
TFDSuggestionViewController *suggController = ((TFDSuggestionViewController *)[segue destinationViewController]);
suggController.myPopoverController = ((UIStoryboardPopoverSegue *)segue).popoverController;
((UIStoryboardPopoverSegue *)segue).popoverController.delegate = self;
}
}
Thank you for you advice!
In the old days of manual memory management there was something called a reference count. It was essentially the number of times an object was retained (strong reference) by other objects or the App. In the more recent ARC (Automatic reference counting) we no longer need to do [object retain] and [object release]. These actions are handled for us by the compiler.
Now to your situation. A weak reference does not increase the reference count of the object you are referencing. So if you create an object in a scope, assign a weak reference to it, then leave the scope your object's reference count is 0.
-(void)SomeMethod
{
ClassObject *object = [[ClassObject alloc] init];
//object's internal reference count is now 1
self.myPopoverController = object;
//Since myPopoverController is a weak reference, object still has reference count of 1
//Some other code that does things and stuff.
}
//We just closed the scope, so object's reference count is now 0
//ARC is free to release the object to free it's memory, causing any
//weak references to return nil
In the example above it shows a very simple object life cycle. Once you understand the life cycle you can see why a weak reference will do you absolutely no good in this situation.
As to why it worked in iOS7 and not in iOS8 the only answer that I have is that iOS8 is likely much more efficient in garbage collection. If you ran it a million times in iOS7 I'm sure you would find at least one example of the exact same problem happening. It was a flaw in the code that the new OS makes more prevalent.
If you want the object to stay alive you need to have at least one strong reference to it. The only precaution is that when you call dismiss you should nil the strong reference. Then there should be no adverse memory issues to resolve.
Another bit that is very important. The UIPopoverController is not the same object as what is visible on screen. What is visible on screen is the UIPopoverController.view. The view is still retained by the view hierarchy, but the controller needs to be retained by you in order for it not to get released. Once the UIPopoverController is released the view's delegate will be nil since view.delegate is also a weak reference.
Study the object lifecycle. It will help you avoid garbage collection problems that will definitely arise in the future as the OS gets more and more efficient in memory handling.
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.
For example, say I have a RootViewController class and AnotherViewController class, and I need to change a property in my RootViewController from AnotherViewController... is it safe to have a "RootViewController" property in AnotherViewController.h (so that I can access its instance variables)?
#interface AnotherViewController : UIViewController {
RootViewController *rootViewController;
}
#property (nonatomic, retain) RootViewController *rootViewController;
#end
#implementation AnotherViewController
#synthesize rootViewController;
- (void)someMethod {
// set the data was added flag, so the rootViewController knows to scroll to the bottom of the tableView to show the new data
self.rootViewController.dataWasAdded = YES;
// if the user came in via a search result, make the search controller's tableView go away
self.rootViewController.searchDisplayController.active = NO;
}
If that's not a good idea, can anybody explain why?
In the code above, I know I could have used a protocol/delegate to handle the same thing - and I'm guessing I probably should. However, none of the books or other materials I've read has really discussed this.
The reason I'm asking is that I'm in the process of making my app universal, and using a UISplitViewController I've noticed that I need to often update my "master view" as the user makes changes in the "detail view". So, I took what seemed the easy route and started setting UIViewControllers as properties... but I'm experiencing some hard to track memory leaks and occasional crashes. I read something about "circular references", and wonder if that could be part of the issue (I do have a couple of places where UIViewControllers are set as properties of one another).
Thanks for any insight, or pointers to reference materials that cover this.
I'd avoid making a habit of this as there are better safer alternatives. Using a protocol/delegate is the preferred Apple way of managing data across classes. You can also set up NSNotifications to send/trigger data/events from one class to another. Key Value Observing (KVO) is also a decent way to listen in for changes.
In MVC structuring, the child views and downstream controllers really should have no idea (aka, keeping references) of their parents. It should always work the other way around where the parents manage and keep track of the children.