I have a simple app with two basic screens, a UIMapView and a UITableView. I'd like to have a toolbar at the bottom with a couple of buttons and a UISegmentedControl with two segments: "Map" and "Table". (The layout is similar to the Google Maps app that ships with the iPhone.) How can I keep the same toolbar while presenting either the UIMapView (with a UIMapViewController) or the UITableView (with a UITableViewController) when the user switches back and forth on the segmented control? Of course, I can just create an identical toolbar for each of the two different views and display them separately, but is there a better way?
Write a UIViewController that manages your 2 VC's and transitions between the MKMapView and UITableView in response to the segmented control.
First set up the nib for this new VC in Interface Builder: add an UISegementedControl and a simple UIView (contentView).
The interface file contains references to the UI Elements and to the 2 VC's + an action to respond to the segmented control:
//
// MapAndTableViewController.h
//
#import <UIKit/UIKit.h>
#import "MyMapViewController.h"
#import "MyTableViewController.h"
#interface MapAndTableViewController : UIViewController {
IBOutlet UISegmentedControl* segmentedControl;
IBOutlet UIView* contentView;
UIViewController* firstVC;
UIViewController* secondVC;
}
-(IBAction) valueChanged:(UISegmentedControl*) sender;
#end
Implementation:
//
// MapAndTableViewController.m
//
#import "MapAndTableViewController.h"
#implementation MapAndTableViewController
-(IBAction) valueChanged:(UISegmentedControl*) sender {
if (sender.selectedSegmentIndex == 0) {
[UIView transitionFromView:[contentView.subviews lastObject] toView:firstVC.view duration:0.5 options:UIViewAnimationOptionTransitionFlipFromLeft completion:nil];
}
if (sender.selectedSegmentIndex == 1) {
[UIView transitionFromView:[contentView.subviews lastObject] toView:secondVC.view duration:0.5 options:UIViewAnimationOptionTransitionFlipFromLeft completion:nil];
}
}
-(void)awakeFromNib {
firstVC = [[MyMapViewController alloc] initWithNibName:#"MyMapViewController" bundle:nil];
secondVC = [[MyTableViewController alloc] initWithNibName:#"MyTableViewController" bundle:nil];
}
- (void)viewDidLoad {
[super viewDidLoad];
[contentView addSubview:firstVC.view];
}
- (void)dealloc {
[firstVC release];
[secondVC release];
[super dealloc];
}
#end
In the valueChanged method you replace the current view and animate the transition.
Note that the views firstVC.view and secondVC.view are created on first access of the view-property of each VC.
you could use a single view controller, and add all of the views(UIMapView, UITableView, etc) to your view and simply show/hide the correct views upon clicking the segmented control
with such a simple app without many views, you shouldn't have a messy/clustered view controller file and can easily show/hide these 2 views.
perhaps use an animation between switching between views so it looks nice
Related
Trying to achieve
When I tap on the tabbaritem say #2, it will called the method and reload the web view.
Issue
When I tap on the tabbaritem, the method is called but web view did not reload.
Did not load the web view
Question
If I called the method on the VC itself. I can manage to reload the web view. Only if I called it when the tabbaritem is tapped, it doesn't reload the web view.
Code
MyTabBarController.m
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
NSLog(#"controller class: %#", NSStringFromClass([viewController class]));
NSLog(#"controller title: %#", viewController.title);
if (viewController == [tabBarController.viewControllers objectAtIndex:2])
{
[(UINavigationController *)viewController popToRootViewControllerAnimated:YES];
tabBarController.delegate = self;
[[[Classes alloc] init] LoadClasses];
}else if (viewController == [tabBarController.viewControllers objectAtIndex:3]){
[(UINavigationController *)viewController popToRootViewControllerAnimated:YES];
tabBarController.moreNavigationController.delegate = self;
[[[Gym alloc] init] handleRefreshGym:nil];
}else{
[(UINavigationController *)viewController popToRootViewControllerAnimated:NO];
}
}
Classes.m
- (void)LoadClasses {
sURL = #"www.share-fitness.com/apps/class.asp?memCode=SF100012&dtpClass=13/09/2018&lang=EN&lat=37.785835&long=-122.406418&ver=1&plat=IOS"
NSLog(#"The URL To be loaded %#", sURL);
NSURL *url = [NSURL URLWithString:sURL];
sRefresh = sURL;
[[NSURLCache sharedURLCache] removeAllCachedResponses];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
[webView loadRequest:urlRequest];
[webView setDelegate:(id<UIWebViewDelegate>)self];
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(handleRefresh:) forControlEvents:UIControlEventValueChanged];
[webView.scrollView addSubview:refreshControl];
}
As I mentioned in my other reply Objective-C: How to properly set didSelectViewController method for TabBarController, so I can refresh the VC everytime it is tapped, I don't think it's good User Experience to be refreshing the view from the server every time the tab bar is selected (this will get very annoying for users to wait every time for the server to refresh the data)
That being said, the issue with the code you posted is that you're initializing a new instance of your classes in the TabBarControllerDelegate method so the method will be called on this new instance instead of on the one that's displaying/exists in your TabBarController's view controllers. Specifically these two lines are initializing the new instances:
[[[Classes alloc] init] LoadClasses];
[[[Gym alloc] init] handleRefreshGym:nil];
Instead you should be finding the instance that already exists, and calling the method on them.
I would recommend creating a ParentViewController with a public method along the lines of - (void)doStuffWhenTabBarControllerSelects; (just example naming to be clear what's it doing to you) then have each of the view controllers you'd like to have do something when they're selected be child classes of this parent (and have their own implementation of - (void)doStuffWhenTabBarControllerSelects;). This way in the TabBarController's delegate method, you can just find the appropriate instance of ParentViewController (associated with the view controller being selected) and call the - (void)doStuffWhenTabBarControllerSelects; method on it.
Here's an example of what I mean:
ParentViewController.h:
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface ParentViewController : UIViewController
- (void)doStuffWhenTabBarControllerSelects;
#end
NS_ASSUME_NONNULL_END
ParentViewController.m:
#import "ParentViewController.h"
#interface ParentViewController ()
#end
#implementation ParentViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)doStuffWhenTabBarControllerSelects {
NSLog(#"Fallback implementation if this method isn't implemented by the child class");
}
#end
FirstViewController.h:
#import <UIKit/UIKit.h>
#import "ParentViewController.h"
#interface FirstViewController : ParentViewController
#end
FirstViewController.m:
#import "FirstViewController.h"
#interface FirstViewController ()
#end
#implementation FirstViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)doStuffWhenTabBarControllerSelects {
NSLog(#"I'm doing stuff on the %# when the tab bar controller delegate calls back to selection", NSStringFromClass([self class]));
}
#end
SecondViewController.h:
#import <UIKit/UIKit.h>
#import "ParentViewController.h"
#interface SecondViewController : ParentViewController
#end
SecondViewController.m:
#import "SecondViewController.h"
#interface SecondViewController ()
#end
#implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)doStuffWhenTabBarControllerSelects {
NSLog(#"I'm doing stuff on the %# when the tab bar controller delegate calls back to selection", NSStringFromClass([self class]));
}
#end
MyTabBarController.h:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface MyTabBarController : UITabBarController <UITabBarControllerDelegate>
#end
NS_ASSUME_NONNULL_END
MyTabBarController.m:
#import "MyTabBarController.h"
#import "ParentViewController.h"
#implementation MyTabBarController
- (void)viewDidLoad {
[super viewDidLoad];
self.delegate = self;
}
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
// since your view controllers are embedded in nav controllers, let's make sure we're getting a nav controller
if ([viewController isKindOfClass:[UINavigationController class]]) {
// we're expecting a nav controller so cast it to a nav here
UINavigationController *navController = (UINavigationController *)viewController;
// now grab the first view controller from that nav controller
UIViewController *firstViewControllerInNav = navController.viewControllers.firstObject;
// check to make sure it's what we're expecting (ParentViewController)
if ([firstViewControllerInNav isKindOfClass:[ParentViewController class]]) {
// cast it to our parent view controller class
ParentViewController *viewControllerToCallMethodOnAfterSelection = (ParentViewController *)firstViewControllerInNav;
[viewControllerToCallMethodOnAfterSelection doStuffWhenTabBarControllerSelects];
}
}
}
#end
Then when you select between the two tabs you'll this is the output:
I'm doing stuff on the FirstViewController when the tab bar controller delegate calls back to selection
I'm doing stuff on the SecondViewController when the tab bar controller delegate calls back to selection
I'd recommend doing some additional research/reading of the documentation:
There's a good amount of beginner information here: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/DefiningClasses/DefiningClasses.html#//apple_ref/doc/uid/TP40011210-CH3-SW1
UITabBarController: https://developer.apple.com/documentation/uikit/uitabbarcontroller?language=objc
UITabBarControllerDelegate:
https://developer.apple.com/documentation/uikit/uitabbarcontrollerdelegate?language=objc
One other helpful hint is that within Xcode you can hold down on the option key and click on something to show a quicklook into the explanation/documentation
You can also right click on something and "Jump To Definition". The majority of Apple's implementations will will have additional information in the header.
Here's the example of what's in the header of UITabBarController:
/*!
UITabBarController manages a button bar and transition view, for an application with multiple top-level modes.
To use in your application, add its view to the view hierarchy, then add top-level view controllers in order.
Most clients will not need to subclass UITabBarController.
If more than five view controllers are added to a tab bar controller, only the first four will display.
The rest will be accessible under an automatically generated More item.
UITabBarController is rotatable if all of its view controllers are rotatable.
*/
NS_CLASS_AVAILABLE_IOS(2_0) #interface UITabBarController : UIViewController <UITabBarDelegate, NSCoding>
As well as under the Help Menu there's "Developer Documentation" (CMD + SHIFT + 0) which has a multitude of useful information.
Lets say I have a UIViewController with two buttons, both going (push) to another UIViewController that has two UIWebViews (showing two different PDF files), how can I make sure that only the one I choose via the button is showed?
You need to pass some information to the UIViewController which has the UIWebViews, saying which button was pressed. Then, based on that information, decide which of the UIWebViews to display.
As you are using storyboards, I suggest you look into prepareForSegue. It will allow you to set a property on the destination view controller with something like the following. You should add this to the UIViewController which contains the buttons.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"buttonOne"]) {
ExampleViewController *destViewController = segue.destinationViewController;
destViewController.buttonClicked = #"One";
} else if ([segue.identifier isEqualToString:#"buttonTwo"]) {
ExampleViewController *destViewController = segue.destinationViewController;
destViewController.buttonClicked = #"Two";
}
}
You can then use the buttonClicked property in the destination view controller to decide which you should display. If you have two separate UIWebViews, you could choose to hide one using webViewOne.hidden = YES; and show the other using webViewTwo.hidden = NO;.
However, it would probably be neater to only have a single UIWebView. You could then use prepareForSeque to pass in the URL of the PDF you would like it to display, rather than just sending the name of the button clicked.
Assuming you webView is in a view controller called SecondViewController and your buttons are in the view controller called FirstViewController
1) Create an object in your SecondViewController.h
#interface SecondViewController : UIViewController
#property (nonatomic, strong) NSString *whichButtonClicked;
#end
2) Import SecondViewController in your FirstViewController
#import "SecondViewController.h"
3) In you button IBAction method in FirstViewController.m . use this code
- (IBAction) firstButtonClicked
{
SecondViewController *secondViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"secondView"];
secondViewController. whichButtonClicked = #"first"
[self.navigationController pushViewController:secondViewController animated:YES];
}
- (IBAction) secondButtonClicked
{
SecondViewController *secondViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"secondView"];
secondViewController. whichButtonClicked = #"second"
[self.navigationController pushViewController:secondViewController animated:YES];
}
PS Don't forget. In you Storyboard. Set Storyboard ID for SecondViewController as secondView
4) In your SecondViewController.m use this code to check which button
if ([self.whichButtonClicked isEqualToString:#"first"])
{
///display first web view here
}
else
{
//display second web view here
}
Hope this helps
I am making a game like Flappy Bird. How do I present a UIViewController from SKScene?
First of all, I tell my environments
Mac OS X 10.9
Xcode 5.0.2
Sprite Kit(framework), social.framework(framework) are added in my project
My goal is to display a "Share" button upon Game Over. Tapping the share button image should present a SLComposeViewController (Twitter Share). The contents of the scene should not change.
I'd like to solve bellow issue and change display from GameOverScene to tweetSheet(display)
composed with social.framework.
The issue
[self presentViewController:tweetSheet animated:YES completion:nil];
//Error:No visible #interface for 'GameOverScene' declares the selector "presentViewController":animated:completion:
My coding files are below(I extracted parts of important codes).
ViewController.h
import <UIKit/UIKit.h>
import <SpriteKit/SpriteKit.h>
import <iAd/iAd.h>
#interface ViewController : UIViewController<ADBannerViewDelegate><br>
#end
GameOverScene.h
#import <SpriteKit/SpriteKit.h>
#class SpriteViewController;
#interface GameOverScene : SKScene {
}
#end
GameOverScene.m
#import "GameOverScene.h"
#import "NewGameScene.h"
#import "MainScene.h"
#import <Social/Social.h>
#implementation GameOverScene {
//The twitter button
SKSpriteNode *_twitterbutton;
}
- (id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size]) {
//Creating the twitterbutton with the twitterbutton image from Images.xcassets
_twitterbutton = [SKSpriteNode spriteNodeWithImageNamed:#"twitterbutton"];
[_twitterbutton setSize:CGSizeMake(50, 50)];
[_twitterbutton setPosition:CGPointMake(self.size.width/2, self.size.height/5 + 50)];
//Adding the twitter button
[self addChild:_twitterbutton];
//Again, this is important, otherwise we can't identify what button is pressed
_twitterbutton.name = #"twitterbutton";
[_twitterbutton setPosition:CGPointMake(self.size.width/2, self.size.height/5 + 50)]
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//Same as in NewGameScene menu
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
//Is the twitter button touched?
if([node.name isEqualToString:#"twitterbutton"]){
if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter]){
SLComposeViewController *tweetSheet = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
[tweetSheet setInitialText:#"TestTweet from the Game !!"];
[self presentViewController:tweetSheet animated:YES completion:nil];
**//Error:No visible #interface for 'GameOverScene' declares the selector "presentViewController":animated:completion:**
}
}
ViewControlloer.m
#import "ViewController.h"
#import "NewGameScene.h"
#implementation ViewController
//Loads the view onto our main class
- (void)loadView
{
self.view = [[SKView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
}
//Executes when view finishes loading
- (void)viewWillLayoutSubviews
{
[super viewDidLoad];
//Set the resize mode to flexible width and height
[self.view setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
//Create our view from our original view
//Make sure to leave originalContentView in, otherwise the app will crash
SKView *skView = (SKView *)self.originalContentView;
//We create a new NewGameScene according to the current dimensions
SKScene *scene = [NewGameScene sceneWithSize:skView.bounds.size];
//Create a transition class with animation type fade and a duration of .4 seconds
SKTransition *transition = [SKTransition fadeWithDuration:.4];
//Present the menu view (NewGameScene) with our fade in transition
[skView presentScene:scene transition:transition];
}
#end
You cannot present a viewController from within a SKScene as it is actually only being rendered on a SKView. You need a way to send a message to the viewController, which in turn will present the viewController. For this, you can use delegation.
Add the following protocol definition to your SKScene's .h file:
#protocol sceneDelegate <NSObject>
-(void)showShareScreen;
#end
And declare a delegate property in the interface:
#property (weak, nonatomic) id <sceneDelegate> delegate;
Then, at the point where you want to present the share screen, instead of the line:
[self presentViewController:tweetSheet animated:YES completion:nil];
Use this line:
[self.delegate showShareScreen];
Now, in your viewController's .h file, implement the protocol:
#interface ViewController : UIViewController <sceneDelegate>
And, in your .m file, add the following line before you present the scene:
scene.delegate = self;
Then add the following method there:
-(void)presentShareScreen
{
if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])
{
SLComposeViewController *tweetSheet = [SLComposeViewController
composeViewControllerForServiceType:SLServiceTypeTwitter];
[tweetSheet setInitialText:#"TestTweet from the Game !!"];
[self presentViewController:tweetSheet animated:YES completion:nil];
}
}
An alternate method would be to use NSNotificationCenter
Keep the -presentShareScreen method as described in the previous alternative.
Add the viewController as a listener to the notification in it's -viewDidLoad method:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(presentShareScreen) name:#"showShareScreen" object:nil];
Then, in the scene at the point where you want to show this viewController, use this line:
[[NSNotificationCenter defaultCenter] postNotificationName:#"showShareScreen" object:nil];
So I've a button with a IBAction heading to another ViewController by its initWithNibName method. Everything is embedded in a NavigationController.
I also created a .xib for this ViewController, here is a quick screenshot :
Here is my code :
.h
#interface ModeEmploiController : UIViewController
{
IBOutlet UIScrollView *scrollView;
UITextView *vueOffres, *vueInfos, *vueGrilles;
}
#property (nonatomic, retain) IBOutlet UIScrollView *scrollView;
.m
#implementation ModeEmploiController
#synthesize scrollView;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
[scrollView setScrollEnabled:YES];
[scrollView setContentSize:CGSizeMake(320, 529)];
// Here I define vueOffres, vueInfos and vueGrilles and for each of them :
[self.view addSubview:vueGrilles/vueInfos/vueOffres];
}
}
But when I run my app, my scroll isn't enabled and I don't have the navigation bar of my navigation controller. What's happening?
add another view inside the ScrollView i called mine content view. the do
self.scrollView.contentSize = self.contentView.frame.size;
for the navigation bar you need to have a uinavigation controller and make your controller the rootviewcontroller of the navigation controller. like this
-(IBAction)MyButton:(id)sender
{
MyViewController *controller = [[MyViewController alloc] initWithNibName:#"MyView" bundle:nil];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller];
[self.navigationController presentModalViewController:navController animated:YES];
}
Remove your ModeEmploiController from xib, but keep its child View and ScrollView.
now click on File's Owner and put ModeEmploiController from Identity inspector in right panel.
Now right click on File's Owner and connect property of view to View and scrollView to ScrollView.
Your initialization code is in the wrong method.
Since you are using Storyboards, your view controller is being unarchived from a nib file. The correct place to initialize the controls is in the awakeFromNib method.
Make sure that you have set up an IBOutlet property for your scroll view and hooked it up in the Storyboard, and then:
- (void)awakeFromNib {
[self.scrollView setScrollEnabled:YES];
[self.scrollView setContentSize:CGSizeMake(320, 529)];
// Here I define vueOffres, vueInfos and vueGrilles and for each of them :
[self.view addSubview:vueGrilles/vueInfos/vueOffres];
}
That just leaves the problem of the subview that you are adding. What is vueGrilles/vueInfos/vueOffres? You should create this view properly within the viewDidLoad method and add it as a subview there instead of in this initialiser.
Hey guys I'm a newbie developer with cocoa and I'm Trying to create a simple app. which display 4 differents pages that you can select via a tab bar.
My Problem: I made a UIButton on the First/Home page (==>FirstView.xib) with IB and i tried to link it to my second page (==>SecondView.xib) with some code found on the net.
The thing is that i can build my code but nothing happens when I try to click on my button, can you help me ?
Code for FirstView.h:
#import <UIKit/UIKit.h>
#interface FirstViewController : UIViewController {
UIButton *button;}
#property (nonatomic, retain) IBOutlet UIButton *button;
- (IBAction)goToViewTwo;
#end
Code for FirstView.m:
#implementation FirstViewController
#synthesize button;
- (IBAction)goToViewTwo {
SecondViewController *SecondView= [[SecondViewController alloc] initWithNibName: #"SecondView" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:SecondView animated:YES];
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)dealloc {
[super dealloc];
[button release];
}
#end
About the outlets in FIrstVIew.xib:
I linked "Touch Up Inside" to "goToViewTwo" and "b^utton" to "File's Owner'
I'm under the impression this is in a tab bar where each tab bar button displays a .xib file. If this is the case, the solution is fairly simple. Your tabBarController manages your views in an array of view controllers. To switch between them, your IBAction method should be the following:
- (IBAction)goToViewTwo {
self.tabBarController.selectedIndex = 1;
}
This tells your tab bar controller to switch from your current view controller (at index 0) to the second (at index 1)