Message box to TabBarController - uitabbarcontroller

I created a message box into the FirstViewController of my TabBarController:
- (void)pressedButton:(id)sender {
[[[UIAlertView alloc] initWithTitle:#"Snap it" message:#"Take a picture"
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil]
show];
When the user clicks on "Ok", I want them to be redirected to the SecondViewController.
Any idea how to do this?
Thanks in advance.

If you have your tab bar controller connected to a "IBOutlet" (or have some other reference to it), switching tabs is as easy as updating the selectedIndex property (I've linked the Apple documentation for you).
EDIT:
Change your code to this.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
// if you have connected your tab bar controller to an IBOutlet named myTabBarController
if(myTabBarController)
{
// first tab bar controller is zero, second tab bar controller is 1, etc.
myTabBarController.selectedIndex = 1;
} else {
NSLog( #"tabBarController is nil and probably not set correctly" );
}
}
- (void)pressedButton:(id)sender {
UIAlertView * alertView =
[[UIAlertView alloc] initWithTitle:#"Snap it" message:#"Take a picture" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
if(alertView)
{
[alertView show];
}
}
This assumes you've set your tab bar controller to an IBOutlet correctly. Notice also that I've set the "delegate" on the alert view to "self" (i.e. the "clickedButtonAtIndex" method has to live in the same object & class if using "self" as a delegate).

Related

How do I hide default CommandMenu in SwiftUI on macOS?

I'm (attempting) switching over my AppDelegate macOS app to the SwiftUI lifecycle - but can't seem to find out how to handle the CommandMenu. I just want to delete these default menu items (Fie, Edit, View, etc...). In the past, I would just delete them from the Storyboard - but I'm not using a storyboard here. Is there a way to delete these items in SwiftUI?
The items I want to delete:
I know how to add new items via:
.commands {
MyAppMenus()
}
But that just adds them inline with the existing menu items.
swiftUI -- override AppDelegate with your custom:
#main
struct PixieApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
///........
}
code of appDelegate:
final class AppDelegate: NSObject, NSApplicationDelegate {
func applicationWillUpdate(_ notification: Notification) {
if let menu = NSApplication.shared.mainMenu {
menu.items.removeFirst{ $0.title == "Edit" }
menu.items.removeFirst{ $0.title == "File" }
menu.items.removeFirst{ $0.title == "Window" }
menu.items.removeFirst{ $0.title == "View" }
}
}
}
result:
Until SwiftUI adds more support for adjusting menus, I think you have to worry about SwiftUI reseting the NSApp.mainMenu whenever it updates a window.body. I haven't tried every method for adjusting the mainMenu, but of the methods I tried, the flaw was that SwiftUI seems to have no check for whether it last set NSApp.mainMenu or if something else did.
So however you are managing the menu, update it after SwiftUI has.
Use KVO and watch the NSApp for changes on .mainMenu. Then make your changes with a xib, or reseting the whole thing, or editing SwiftUI's menus.
Example:
#objc
class AppDelegate: NSObject, NSApplicationDelegate {
var token: NSKeyValueObservation?
func applicationDidFinishLaunching(_ notification: Notification) {
// Adjust a menu initially
if let m = NSApp.mainMenu?.item(withTitle: "Edit") {
NSApp.mainMenu?.removeItem(m)
}
// Must refresh after every time SwiftUI re adds
token = NSApp.observe(\.mainMenu, options: .new) { (app, change) in
// Refresh your changes
guard let menu = app.mainMenu?.item(withTitle: "Edit") else { return }
app.mainMenu?.removeItem(menu)
}
}
}
struct MarblesApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some View {
//...
}
}
This seems to work in Xcode 13.4.1 with Swift 5 targeting macOS 12.3.
Hopefully Apple adds greater control soon. It seems Catalyst has other options. Or you can create a traditional AppKit app and insert the SwiftUI views into it.
You can remove command menu items through the AppDelegate file:
override func buildMenu(with builder: UIMenuBuilder) {
super.buildMenu(with: builder)
builder.remove(menu: .services)
builder.remove(menu: .format)
builder.remove(menu: .toolbar)
}
This thread on the Apple Developer forum might help as well: https://developer.apple.com/forums/thread/649096
CommandGroup(replacing: CommandGroupPlacement.appVisibility, addition: {})

iOS8 TabbarController inside a UISplitviewController Master

I've tried to expand the default Apple MasterDetail Template by adding a UITabbarController in front of the UINavigationController of the MasterView, so there is a structure like this:
UISplitViewController (Master) > UITabbarController > UINavigationController > UITableViewController
But if I run the App, after changing application(didFinishLaunchingWithOptions) to use the correct ViewController, and try to perform the ShowDetails Segue the DetailsView ist presented Modally on the iPhone. On the other side the iPad Version is working as expected.
What am I forgot to do? Or how can I fix it?
I figured out how to put the detail on to the master's UINavigationController instead of presenting it modally over the UITabBarController.
Using the UISplitViewControllerDelegate method
- splitViewController:showDetailViewController:sender:
In case the UISplitViewController is collapsed get the masters navigation controller and push the detail view onto this navigation controller:
- (BOOL)splitViewController:(UISplitViewController *)splitViewController
showDetailViewController:(UIViewController *)vc
sender:(id)sender {
NSLog(#"UISplitViewController collapsed: %d", splitViewController.collapsed);
// TODO: add introspection
if (splitViewController.collapsed) {
UITabBarController *master = (UITabBarController *) splitViewController.viewControllers[0];
UINavigationController *masterNavigationController = (UINavigationController *)master.selectedViewController;
// push detail view on the navigation controller
[masterNavigationController pushViewController:vc animated:YES];
return YES;
}
return NO;
}
Just to update the answers above. Since you can't push navigation controllers anymore, you have to push its top view controller instead.
func splitViewController(splitViewController: UISplitViewController, showDetailViewController vc: UIViewController, sender: AnyObject?) -> Bool {
if splitViewController.collapsed {
let tabBarController = splitViewController.viewControllers.first as! UITabBarController
let selectedNavigationViewController = tabBarController.selectedViewController as! UINavigationController
// Push view controller
var viewControllerToPush = vc
if let navController = vc as? UINavigationController {
viewControllerToPush = navController.topViewController
}
selectedNavigationViewController.pushViewController(viewControllerToPush, animated: true)
return true
}
return false
}
Here's my solution. Place in MasterViewController.m and remember to give your detail view a Storyboard ID in IB. In my case 'detail'.
-(BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if ([identifier isEqualToString:#"showDetail"] && self.splitViewController.collapsed) {
DetailViewController *myController = (DetailViewController *)[self.storyboard instantiateViewControllerWithIdentifier:#"detail"];
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
[myController setDetailItem:object];
[self.navigationController showViewController:myController sender:self];
return NO;
}
return YES;
}
There is another way to do it without code.
After you embedded the the UINavigationController in the TabBarController embed the TabBarController in another UINavigationController. So you will have: SplitViewController -> Master -> NavCon -> TabBar -> NavCon -> TableViewController.
It's much easier doing like this, but there a bug that I haven't found out how to fix. The navigation bar presented will be that of the TabBarController, not the TableViewController. Any ideas how to fix that?
Subclass TabBarController like this:
- (void)showViewController:(UIViewController *)vc sender:(id)sender
{
if ([self.selectedViewController isKindOfClass:UINavigationController.class])
[self.selectedViewController showViewController:vc sender:sender];
else
[super showViewController:vc sender:sender];
}
- (UIViewController*)separateSecondaryViewControllerForSplitViewController:(UISplitViewController *)splitViewController
{
return [self.selectedViewController separateSecondaryViewControllerForSplitViewController:splitViewController];
}
- (void)collapseSecondaryViewController:(UIViewController *)secondaryViewController forSplitViewController:(UISplitViewController *)splitViewController
{
[self.selectedViewController.navigationController collapseSecondaryViewController:secondaryViewController forSplitViewController:splitViewController];
}
See this question for complete explanation.
Here is an alternative that is based on testing the size classes of the splitViewController :
Use a custom UISplitViewController (subclass)
Override the showDetailViewController operation
Use the traitCollection to determine the class of the UISplitViewController
If the horizontal class is Compact, get the navigationController to call showViewController
Here is the the code of the custom UISplitViewController :
import UIKit
class CustomSplitViewController: UISplitViewController {
override func showDetailViewController(vc: UIViewController!, sender: AnyObject!) {
if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClass.Compact) {
if let tabBarController = self.viewControllers[0] as? UITabBarController {
if let navigationController = tabBarController.selectedViewController as? UINavigationController {
navigationController.showViewController(vc, sender: sender)
return
}
}
}
super.showDetailViewController(vc, sender: sender)
}
}
Do not forget to the set the custom class in the storyboard.
Tested in the simulator of iPhone 6, iPhone 6+ and iPad Air and worked as expected.

Disable rotation for one UITabbar item

I have a uitabbarcontroller with 4 tab bar items and each tab bar item has a uinavigationcontroller.
I needed to lock the orientation of one uitabbar item only to Portrait. So i implemented the following code:
Created a custom tab bar controller and added the following code in it:
MainTabBarController.m
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// You do not need this method if you are not supporting earlier iOS Versions
return [self.selectedViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}
-(NSUInteger)supportedInterfaceOrientations
{
if (self.selectedViewController)
return [self.selectedViewController supportedInterfaceOrientations];
return UIInterfaceOrientationMaskPortrait;
}
-(BOOL)shouldAutorotate
{
return YES;
}
Created a custom navigation controller to use in one of the uitabbaritems and added the following code in it:
-(NSUInteger)supportedInterfaceOrientations
{
return [self.topViewController supportedInterfaceOrientations];
}
-(BOOL)shouldAutorotate
{
return YES;
}
and for the uiviewcontroller in the custom navigation controller i added the following code:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (BOOL)shouldAutorotate
{
return NO;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
The above code works fine. My issue is, if you go to the tabbar item (whose orientation is locked to Protrait) when the device is already in Landscape mode the orientation changes to Landscape. Can anyone please help me how to solve my issue.
Thanks,
Anand.
FYI!!!
I've found a way for my problem. I added the following function to the viewcontroller for which i want the display only in protrait mode:
-(void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
if (UIDeviceOrientationIsLandscape([[UIDevice currentDevice] orientation]))
{
if ([[UIDevice currentDevice] respondsToSelector:#selector(setOrientation:)])
{
int orientationPortrait = UIInterfaceOrientationPortrait;
NSMethodSignature *sig = [[UIDevice currentDevice] methodSignatureForSelector:#selector(setOrientation:)];
NSInvocation* invo = [NSInvocation invocationWithMethodSignature:sig];
[invo setTarget:[UIDevice currentDevice]];
[invo setSelector:#selector(setOrientation:)];
[invo setArgument:&orientationPortrait atIndex:2];
[invo invoke];
}
}
}

How to present a transparent view with tab bar controller?

Currently I have 5 tabs. The third tab is supposed to show a view which takes space only half of the screen, and the rest requires entire screen. I wonder how I can display tab3's view on top of the rest of tabs'. Suppose current selected tab is 2 and the user pressed tab3, it shows tab3's view on top of tab2's view. Is it possible to do that? Or I have to create my own View and fake the tab controller.
Problem Solved;
Not very elegant but it works. I fake Tab 3 with a view controller that has no view and make tabItem 3 as a trigger that presents a view controller.
something like this:
duplicatedTab3 = [[Tab3 alloc] init]
[tabbarController setViewControllers:[NSArray arrayWithObjects:tab1,tab2,tab3,tab4,tab5,nil]];
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
BOOL flag = NO;
NSUInteger tabIndex = [tabBarController.viewControllers indexOfObject:viewController];
if (viewController == [tabBarController.viewControllers objectAtIndex:tabIndex] &&
tabIndex != tabBarController.selectedIndex) {
if ([viewController isMemberOfClass:tab3]) {
if (![duplicatedTab3 isShowing])
[duplicatedTab3 show];
else
[duplicatedTab3 hide];
}
else {
flag = YES;
}
}
return flag;
}

TabBarController, overwriting shouldSelectViewController to do a segue

Hi Im trying to change the tabcontroller flow, so when a user is not loged in just take him to the login view instead the settings one. My controller extends TabBarController and I set the delegate as
self.tabBarController.delegate=self;
My Code is:
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
if (login) {
LoginViewController *loginViewController = [[LoginViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc]
initWithRootViewController:loginViewController];
[tabBarController presentViewController:loginViewController animated:YES completion:nil];
return NO;
} else {
return YES;
}
I never manage to do the navigation it gives an excetion :
ion 'NSInvalidArgumentException', reason: 'Application tried to present modally an active controller <UITabBarController: 0x6a72220>.
I also tried to show the login in as a modal but it only shows a black screen:
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
if (YES) {
LoginViewController *loginViewController = [[LoginViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc]
initWithRootViewController:loginViewController];
[tabBarController presentModalViewController:navController animated:YES];
return NO;
} else {
return YES;
}
}
Can anybody help Me!!!! please!!!!
Well I manage to fix the black modal screen (still cant d a segue that is not modal).
The problem was that as I am using storyboard I have to load the view from story board as follows.
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController *navController = [storyboard instantiateViewControllerWithIdentifier:#"login"];
[navController setModalPresentationStyle:UIModalPresentationFullScreen];
[self presentModalViewController:navController animated:YES];
That made the trick :)