I am using a tab bar (UITabBarController) on my app and I wish to customize the appearance of the table that appears when you click the more button. I have worked out how to
Following on from Stephan's suggestion to replace the dataSource of the moreNavigationController, here is a quick over view of the code I implemented.
I created a new class called MoreTableViewDataSource which implements the UITableViewDataSource protocol. The controller which the more page actually uses to build the table is called the UIMoreListControllerModern, and this implements just the required parts of the UITableViewDataSource protocol. My implementation looks like this.
-(MoreTableViewDataSource *) initWithDataSource:(id<UITableViewDataSource>) dataSource
{
self = [super init];
if (self)
{
self.originalDataSource = dataSource;
}
return self;
}
- (void)dealloc
{
self.originalDataSource = nil;
[super dealloc];
}
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section
{
return [originalDataSource tableView:table numberOfRowsInSection:section];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [originalDataSource tableView:tableView cellForRowAtIndexPath:indexPath];
cell.textColor = [UIColor whiteColor];
return cell;
}
and then in my CustomTabBarController class I override viewDidLoad as follows:
- (void)viewDidLoad {
[super viewDidLoad];
UINavigationController *moreController = self.moreNavigationController;
moreController.navigationBar.barStyle = UIBarStyleBlackOpaque;
if ([moreController.topViewController.view isKindOfClass:[UITableView class]])
{
UITableView *view = (UITableView *)moreController.topViewController.view;
view.backgroundColor = [UIColor blackColor];
moreTableViewDataSource = [[MoreTableViewDataSource alloc] initWithDataSource:view.dataSource];
view.dataSource = moreTableViewDataSource;
}
}
As requested here are the header files
@interface MoreTableViewDataSource : NSObject <UITableViewDataSource>
{
id<UITableViewDataSource> originalDataSource;
}
@property (retain) id<UITableViewDataSource> originalDataSource;
-(MoreTableViewDataSource *) initWithDataSource:(id<UITableViewDataSource>) dataSource;
@end
and
#import "MoreTableViewDataSource.h"
@interface CustomTabBarController : UITabBarController
{
MoreTableViewDataSource *moreTableViewDataSource;
}
I followed Ian's implementation to customize the More menu, but I was having a problem retaining the customizations after a memory warning. didReceiveMemoryWarning seems to destroy the UITableView, and when it is regenerated it gets its old dataSource back. Here's my solution:
I replace viewDidLoad on the CustomTabBarController with this:
- (void)viewDidLoad {
[super viewDidLoad];
UINavigationController* moreController = self.moreNavigationController;
if ([moreController.topViewController.view isKindOfClass:[UITableView class]]) {
moreController.delegate = self;
self.moreControllerClass = [moreController.topViewController class];
UITableView* view = (UITableView*) moreController.topViewController.view;
self.newDataSource = [[[MoreDataSource alloc] initWithDataSource:view.dataSource] autorelease];
}
}
As you can see, I added a few properties for storing things I needed. Those have to be added to the header and synthesized. I also made CustomTabBarController a UINavigationControllerDelegate in the header. Here's the delegate function I added:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if ([viewController isKindOfClass:self.moreControllerClass]) {
UIView* view = self.moreNavigationController.topViewController.view;
if ([view isKindOfClass:[UITableView class]]) {
UITableView* tview = (UITableView*) view;
tview.dataSource = self.newDataSource;
tview.rowHeight = 81.0;
}
}
}
This way I make sure my custom data source is always used, because I set it that way just prior to showing the UIMoreListController, every time it's shown.
This works for me in iOS 13, Swift 5.1:
extension MyTabBarController: UITabBarControllerDelegate {
// handle a select of the More tab
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
// style all the tab bar windows and the More tab bar tableview
if viewController == moreNavigationController,
let moreTableView = moreNavigationController.topViewController?.view as? UITableView {
view.tintColor = .systemOrange
moreNavigationController.navigationBar.tintColor = .systemOrange
moreTableView.tintColor = .systemOrange
moreTableView.backgroundColor = UIColor(named: "Your Color")
moreTableView.visibleCells.forEach {
$0.backgroundColor = UIColor(named: "Your Color")
}
}
}
}
@interface TabBarViewController () <UITableViewDelegate,UITableViewDataSource>
@property (nonatomic,strong) UITableView* tabBarTableView;
@property (nonatomic,weak) id <UITableViewDelegate> currentTableViewDelegate;
@end
@implementation TabBarViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self costumizeMoreTableView];
}
-(void)costumizeMoreTableView{
_tabBarTableView = (UITableView *)self.moreNavigationController.topViewController.view;
_currentTableViewDelegate = _tabBarTableView.delegate;
_tabBarTableView.delegate = self;
_tabBarTableView.dataSource = self;
[_tabBarTableView registerNib:[UINib nibWithNibName:@"MoreTabBarTableViewCell" bundle:nil] forCellReuseIdentifier:@"MoreTabBarTableViewCell"];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 120;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 2;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
MoreTabBarTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MoreTabBarTableViewCell" forIndexPath:indexPath];
[cell setMoreTableValues];
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath{
[_currentTableViewDelegate tableView:tableView didSelectRowAtIndexPath:indexPath];
}
@end
visibleCells is populated only after the moreNavigationController is displayed.
And the cells are created at runtime, so even if you change the content of the cells, they are replaced when they are displayed.
One thing to try would be to replace the datasource of the moreNavigationController tableView, call the cellForRowAtIndexPath of the original datasource and change its content before returning it.
Using the code below, after having displayed once the moreNavigationController to initialize it, you'll see that when you return to the moreNavigationController, the cells are red, but return immediately to white background.
UITableView *view = (UITableView *)self.tabBarController.moreNavigationController.topViewController.view;
if ([[view subviews] count]) {
for (UITableViewCell *cell in [view visibleCells]) {
cell.backgroundColor = [UIColor redColor];
}
}
Thanks to Unknown. Following his solution, I will put his code in Swift. Only what you should do more is create MoreTableViewCell class and just it. You don't have to use Storyboard. If you want to modify tableView you can do it in customizeMoreTableView method.
class TabBarMenuController: UITabBarController, UITableViewDelegate, UITableViewDataSource{
var tabBarItems: [UIViewController] = []
var areMessagesVisible: Bool = false
var titleForTabBars: [String] = ["resources", "events", "training", "my profile", "news", "contacts"]
var iconNames: [String] = ["film", "calendar", "classroom", "profile", "news", "Phone"]
var controllersStoryboardId: [String] = ["resourcesNavController", "eventsNavController", "enablementNavController", "profileNavController", "newsNavController", "contactsNavController"]
// to manage moreTableView
var moreTableView: UITableView = UITableView()
var currentTableViewDelegate: UITableViewDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.customizeMoreTableView()
//to REMOVE
areMessagesVisible = true
if !areMessagesVisible{
self.titleForTabBars.removeAtIndex(4)
self.controllersStoryboardId.removeAtIndex(4)
self.iconNames.removeAtIndex(4)
}
for i in 0 ..< controllersStoryboardId.count{
tabBarItems.append(UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier(controllersStoryboardId[i]) as? UINavigationController ?? UINavigationController())
}
self.moreNavigationController.navigationBar.tintColor = UIColor.blackColor()
}
override func viewWillAppear(animated: Bool) {
for i in 0 ..< tabBarItems.count{
tabBarItems[i].tabBarItem = UITabBarItem(title: titleForTabBars[i], image: UIImage(named: iconNames[i]), selectedImage: UIImage(named: iconNames[i]))
}
self.viewControllers = tabBarItems
}
func customizeMoreTableView(){
moreTableView = self.moreNavigationController.topViewController!.view as? UITableView ?? UITableView()
currentTableViewDelegate = moreTableView.delegate;
moreTableView.delegate = self
moreTableView.dataSource = self;
moreTableView.registerClass(MoreTableViewCell.self, forCellReuseIdentifier: "MoreTableViewCell")
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let moreCell = tableView.dequeueReusableCellWithIdentifier("MoreTableViewCell", forIndexPath: indexPath) as? MoreTableViewCell ?? MoreTableViewCell()
moreCell.textLabel?.text = titleForTabBars[indexPath.row + 4]
moreCell.imageView?.image = UIImage(named: iconNames[indexPath.row + 4])
/*let testLabel: UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
testLabel.backgroundColor = UIColor.yellowColor()
moreCell.addSubview(testLabel)
*/
return moreCell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return titleForTabBars.count - 4
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
currentTableViewDelegate?.tableView!(tableView, didSelectRowAtIndexPath: indexPath)
}
}