I am new to IOS, Xcode and MVC. I am on a steep learning curve and am failing with what I assume is a most basic task.
I have a tabbed application with two tabs. Both tab views communicate with a web service and I want to add an image to each tab view, changing the image to indicate the connection state.
So, I created a third .xib file with a controller class (IconViewController). I am hoping to add and remove an instance of this icon view in each of the tab views.
Here is the pseudo code for my icon view:
#interface IconViewController : UIViewController
{
UIImageView *_icon;
}
#property (nonatomic) IBOutlet UIImageView *icon;
- (void)setForBusy;
- (void)setForOk;
- (void)setForFail;
And implementation:
#implementation IconViewController
#synthesize icon = _icon;
-(void)setForBusy
{
// Set Busy Icon Image
}
-(void)setForOk
{
// Set Ok Icon Image
}
-(void)setForFail
{
// Set Fail Icon Image
}
The icon IBOutlet is connected to an UIImageView on the accompanying xib file.
Here is one of the root tab controllers:
#import "IconViewController.h"
#interface TaboneViewController : UIViewController
{
IconViewController *_iconViewController;
}
#property (nonatomic) IBOutlet IconViewController *iconViewController;
and implementation:
#synthesize iconViewController = _iconViewController;
- (void)viewDidLoad
{
[super viewDidLoad];
self.iconViewController = [[IconViewController alloc]
initWithNibName:#"iconViewController"
bundle:nil];
[self.view addSubview:self.iconViewController.view];
}
In the tabView xib Interface Builder I added an Object and made it a class type IconViewController. I connected the Icon View Controller Object->Reference Outlet to the File Owner->iconViewController Outlet.
Running the project I get the error:
loaded the "iconViewController" nib but the view outlet was not set.
I have experimented with other connections but with no luck. It seems to me that my first connection should work but it doesn't.
Any idea what I am misunderstanding? Is the principle good (loading an instance of third view into two root views)? If so, what outlet needs connecting?
Many thanks, Polly
I see your issue. You want to have common stage of image for both tab. I think it is better to implement subclass of UIView (or UIImageView) and implement all methods like set (void)setForBusy and etc. The stage of image you should receive from parent ViewController, something like UINavigationView controller (if you have it). Otherwise you should save stage somewhere else. My personal opinion it is too expensive to create new controller just for your purpose.
Hope it helps.
Related
I have a problem in my iOS application that I'm looking for some help with. I'm relatively new to iOS programming to I'm sure that there is some relatively simple solution to my problem.
First, I'm going to explain the hierarchy of the application:
It uses a UITabBarController to show a couple of different screens.
It uses a SWRevealViewController to show a sidebar
The sidebar is accessed from a Bar button item that is present in the Navigation bar of the application.
That the application uses SWRevealViewController https://github.com/John-Lluch/SWRevealViewController doesn't directly affect the problem that I have. If you are not familiar with this code base, just think of a Simple Bar Button that is shown at all times.
Now to the problem:
The Bar button that I want to show is associated with a few lines of code. (A property declaration and some methods).
This code should be used on a major part of the different view controllers in the application.
Now in the normal case, I'd just subclass UIViewController and make it the superclass of all my views that should show this button. However, my application should also show other types of views, such as a UITableViewController, so subclassing doesn't solve the entire problem.
If Objective-C supported Multiple Inheritance, I would make a class containing this code and let my other classes extend any subclass of UIViewController and my ugly support class at the same time.
Notes:
For now, my app is based on Storyboards
The TabBarController points to a number of UINavigationControllers, and not to any views that doesn't have a Navigation Bar.
I have tried implementing this with Objective-c Category where I add a category to UIViewController that does setup of my UIViewController. But I got stuck on this solution when I needed to add a #Property for the button and linking it to the XIB/Storyboard. Got the idea from this post Add the same UIBarButtonItem to several UIViewControllers but it doesn't contain any details.
tl;dr: I want to show the very same UIBarButtonItem on many of my applications views. The UIBarButtonItem is associated with some code, that is also the same for all these views.
What would be a good way to achieve this?
If i understood you correctly.
You want to show button for all your UIViewControllers.
There are many ways to achieve this. Please try this one.
1. Create a subclass of UIView with its XIB. It contains your button. create Properties and IBAction.
2. Implement your action on this subclass.
3. On .m file write the below code
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder
{
if (![self.subviews count])
{
NSBundle *mainBundle = [NSBundle mainBundle];
NSArray *loadedViews = [mainBundle loadNibNamed:#"MyView" owner:nil options:nil];
return [loadedViews firstObject];
}
return self;
}
where, MyView is the name of your view.
4. Drag and drop a UIViewand place it on every `UIViewController XIB (or storyboard in your case) and set its custom class to "MyView" (or the name of your newly created class).
5. Run your project.
For reference: May i help you?
I have now solved this problem and I will post it here for reference:
This solves the problem with adding a sidebar button to several views in the project, but the code should be usable for any UIBarButton. The solution is the create a Category for UIViewController that specifies a setup method. This setup method is called from the different Views.
Category .h file:
#import <UIKit/UIKit.h>
#interface UIViewController (SidebarCompliance)
- (void)setupSidebar;
#end
Category .m file:
#import "UIViewController+SidebarCompliance.h"
#import "SWRevealViewController.h"
#implementation UIViewController (SidebarCompliance)
- (void)setupSidebar {
SWRevealViewController *revealViewController = self.revealViewController;
if (revealViewController) {
UIBarButtonItem *sidebarButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:#"menu.png"] landscapeImagePhone:[UIImage imageNamed:#"menu.png"] style:UIBarButtonItemStylePlain target:self.revealViewController action:#selector(revealToggle:)];
self.navigationItem.rightBarButtonItem = sidebarButton;
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
}
}
#end
And then an example of one of my views:
View .h file:
#import <UIKit/UIKit.h>
#import "UIViewController+SidebarCompliance.h"
#interface NumeroUno : UIViewController
#end
View .m file:
#import "NumeroUno.h"
#implementation NumeroUno
- (void)viewDidLoad {
[super viewDidLoad];
[self setupSidebar];
}
#end
Newbie question for Xcode gurus...
I have two views. They both use the same custom class. In view_1 I have a button and when this is pressed view_2 will show. In view_2 I have a label which will have it´s text changed when I press the button in view_1. As of now the Label_1 is nil when I set a breakpoint at it and therefor useless. How can I get to update this label when I press the button? Her are some snippets from my code...
This is my .h file:
#interface ViewController : UIViewController
{
IBOutlet UIButton *buttonSelectTimeInterval;
IBOutlet UILabel *labelTimer;
}
#end
This is the button action in my .m file:
- (IBAction)startPouring_ButtonClick:(id)sender
{
labelTimer.text = #"foo";
}
…but my .m file doesn't seem to know the labelTimer since it is a ´nil´. Why is this so? It is instantiated in the .h file.
Anyone?
You can use NSNotificationCenter. Put this in you IBAction.
[[NSNotificationCenter defaultCenter] postNotificationName:#"buttonPressed" object:nil];
And this to your viewDidLoad.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(selectorhere) name:#"buttonPressed" object:nil];
somewhere in your .m
(void)selectorhere {
labelTimer.text = #"foo";
}
You can use NSNotificationCenter for this.
Here are the apple documentation link.
First, nothing is "instantiated" in the .h file - that's just the public listing of what properties and methods are available to other classes. Think of the header file as a table of contents, but only for the things the class wants others to see.
Those properties don't exist in memory until the instance of the class itself is created, and then only if you set them to some initial value once they're needed.
How & where are you creating the 2nd view? Is it a storyboard segue or something? The 1st view doesn't seem to have any way of knowing the 2nd one exists, so it won't be able to see or access the label.
View1Class.m
#import View2Class.h
#implementation View1Class
- (IBAction)startPouring_ButtonClick:(id)sender {
//Instantiate the 2ndView when you need it.
// This gives View1 a reference to View2 and its public UILabel.
View2Class * my2ndView = [[View2Class alloc] init];
my2ndView.labelTimer.text = #"foo";
}
#end
As I said, it's still not clear how/where you're actually displaying the 2nd view though, so the snippet above is incomplete. You could use a modal w/a delegate, or this is where NSNotificationCenter is a helpful option - the 2nd view can sign up to get notifications & change accordingly. There are numerous tutorials about creating a 2nd/modal view and displaying it on a button click - you should probably look at those to clarify how the structure of such an app ought to work.
This answer should get you on the right track.
Other specific issues:
Why is the label nil? Because there isn't one...
In this IBAction, which seems to be in View 1:
- (IBAction)startPouring_ButtonClick:(id)sender
{
labelTimer.text = #"foo"; //this is looking for labelTimer in the clicked view.
}
... it is looking for its own labelTimer IBOutlet (in which case it should probably be self.labelTimer.text), and not that of the 2nd view. If the 1st view doesn't even have a UILabel IBOutlet, this is another problem.
If the views have different functions & different properties, they probably shouldn't be instances of the same custom class. If the 1st view doesn't have or need a UILabel, it shouldn't have one in its .h. If the 2nd view doesn't have or need a button it shouldn't have one in its .h. If the views serve different purposes, then make them different classes.
BTW,
Since you're using instance variables for your IBOutlets, you'd need to write your own getter & setter methods if you want to change their values. Did you? To make those values accessible to other classes, you'd need to make those methods public & put them in the .h. It's not good practice for an instance to set its instance variables directly w/o a getter/setter, and other objects definitely should not.
The preferred method is to use #properties for your IBOutlets instead of declaring them as instance variables. This will automatically create the getter & setter methods, backing store in memory, and as of XCode 4.4 it automatically adds #synthesize so you no longer need to do so. Declaring your IBOutlets as "weak" references prevents retain cycles & memory leak, where the view holds on to the outlets & the outlets hold on to the view & nothing ever goes away...
View1Class.h
#interface ViewController : UIViewController
#property (nonatomic, weak) IBOutlet UIButton *buttonSelectTimeInterval;
#end
View2Class.h
#interface ViewController : UIViewController
#property (nonatomic, weak) IBOutlet UILabel *labelTimer;
#end
I have custom UIButton which programmatically interacts (triggers) with ViewController methods via protocol. But the behaviour of the button has to be dependent on the ViewController placed on. I do this to minimise amount of code in ViewControllers itself, as the button has to remain the same and bear the same functions (navigation).
Is there any way in UIButton's custom class to get the ViewController it is placed on?
I'd follow #rmaddy advice in a specific way, borrowing from the SDK's style
// MyCutomButton.h
#protocol MyCustomButtonDatasource;
#interface MyCustomButton : UIButton
#property(weak,nonatomic) IBOutlet id<MyCustomButtonDatasource>datasource;
// etc
#end
#protocol MyCustomButtonDatasource <NSObject>
#optional
- (NSString *)howShouldIBehave:(MyCustomButton *)button;
#end
Now the button can have it's datasource set in IB. View controllers that include it will need a little additional code (sorry, it's unavoidable in a good design). They will declare themselves as implementing MyCustomButtonDatasource.
When MyCustomButton needs to behave conditionally based on where it's placed, it can ask its datasource...
// MyCustomButton.m
NSString *string = #"defaultBehavior"; // per #RichardTopchiy's suggestion
if ([self.datasource respondsToSelector:#selector(howShouldIBehave:)])
string = [self.datasource howShouldIBehave:self];
// string is just made-up here, have it answer something simple (int, BOOL)
// that lets the button proceed with the right behavior. Don't ask for
// anything that relies on specific knowledge of how MyCustomButton
// is implemented
EDIT - To create the relationship, if you've decorated the property as an IBOutlet (as shown above), you should be able to setup the relationship in IB. Declare your view controller as implementing <MyCustomButtonDatasource>. Select your custom button, then the connections inspector, then drag to your view controller.
Alternatively, make the button itself an IBOutlet property in the view controller and, in viewDidLoad, do:
self.customButton.datasource = self;
The last way to do it is give your button a tag, say, 128, then:
MyCustomButton *customButton = (MyCustomButton *)[self.view viewWithTag:128];
self.customButton.datasource = self;
I have a storyboard driven iOS application.
My objective is to add a small square view (defined as other XIB) to another UIView which is on main ViewController.
*// Please note that, I have assigned the Class names in Identity Inspector correctly*
On the main ViewController, I added a UIView (myHolderView) manually and added a UIView member in the ViewController interface.
//.h
#interface ViewController : UIViewController
#property(nonatomic, strong) IBOutlet UIView *myHolderView;
#end
And, I have connected this IBOutlet by "BlueString" :)
Now, I have added another XIB with single view - Called it MySquare.xib and added a label on it.
And, added a UIView class with a .h and .m files. And, "BlueString" connections are made.
#interface MySquare : UIView
#property(nonatomic, strong) IBOutlet UILabel *myLabel;
#end
Now, in the implementation:
#implementation ViewController
#synthesize myHolderView;
- (void)viewDidLoad
{
[super viewDidLoad];
MySquare *sq=[[MySqure alloc]init];
// Do any additional setup after loading the view, typically from a nib.
[self.myHolderView addSubview:sq];
}
When I run it in the simulator, it just shows the main view, but on it the square view is not coming.
I know that main view had loaded because... I changed the colour of it so that I could see.
Please help.
Probably you don't do in your MySquare.m file the view initialisation using your xib.
- (id) initWithFrame:(CGRect)frame {
NSLog(#"This is called if you add programatically this view");
self = [super initWithFrame:frame];
if (self) {
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"MyIBView" owner:self options:nil] objectAtIndex:0]];
}
return self;
}
If you plan to add your xib using Interface Builder, you have to implement similarly the initWithCoder method:
-(id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"MyIBView" owner:self options:nil] objectAtIndex:0]];
}
return self;
}
Plese go through in this checklist, it might help others too:
Checklist adding a custom view with Interface Builder layout (xib):
1) create a new xib (File->New->IOS/User Interface->View), name it (eg. MyIBView)
2) create a new class (File->New->IOS/Cocoa Touch->Objective-C class), make sure it is a Subclass of UIView! Name it MyIBView.
Now you have MyIBView.xib, MyIBView.h and MyIBView.m files added to your project
3) Edit your MyIBView.xib: select the File's owner and using the Identity inspector (3rd icon) select Class you created recently, MyIBView.
4) Select the top level View (if its a new xib, only this exists) and make sure using the Identity inspector that the Class is UIView (grey) and not overwritten
5) Now you can add IBOutlets and/or IBActions to your MyIBView.h file and you can do connections using Interface Builder in your MyIBView.xib
Using this view programatically in another view:
6a) Create the first method (initWithFrame) above in your MyIBView.m file if you would like to use this custom view programatically
7a) include your MyIBView.h file where you would like to add your custom view programatically
8a) now you can add your view:
MyIBView *myIBView = [[MyIBView alloc] initWithFrame:CGRectMake(0,0,100,100)];
Using this view in Interface Builder in another view:
6b) Create the second method (initWithCoder) above in your MyIBView.m file if you would like to use this custom view in Interface Builder
7b) In your another view drop an UIView to your view canvas. Adjust the size and modify the Class using the Identity inspector to MyIBView.
Please note that you cannot see design time your custom view, only after trying to run the code.
Hope it helps, and you can find your miss-configuration!
I am struggling to make a simple thing (at least I think it's simple) but I just can't do it!
I will try to explain a little bit.
It's an app which displays information. When the user is inside a view, he can click on a button, which displays a popoverview, where he can choose which information he wants to know.
Actually, I can't create an action that changes the UILabel text I created in the main view when the user clicks on the popoverview's buttons.
Anyone has any idea?
Just for you to know: the main view I created a class for it, and also for the popoverview. Although, the popover view I created its design in a XIB file (I don't know if this is important, that's why I am putting this).
Well, I hope you guys were able to understand my question.
Thanks in advance.
Fernando.
Just create a property from the viewcontroller and access it from the consumer (other viewcontroller )
You will have to use delegation in order to see changes in the main view when you are making different actions inside the popover. First, you need to create a protocol inside your popover controller header file:
#import <Foundation/Foundation.h>
#class MyPopoverController;
#protocol MyPopoverDelegate
- (void)valueChanged:(NSString*) newVal;
#end
#interface MyPopoverController: UIPopoverController
#property (weak) id<MyPopoverDelegate> delegate;
#end
Then in .m you implement it like this:
- (void) someActionOccured
{
if([self.delegate respondsToSelector:#selector(valueChanged:)]){
[self.delegate valueChanged:valueYouWantToSendBack];
}
}
Remember that in your main class you have to implement MyPopoverDelegate protocol:
#interface MainViewController: UIViewController <MyPopoverDelegate>
And when you instantiate your popover controller:
/*
** inside MainViewController.m
*/
// remember to assign it's delegate
MyPopoverController *popoverController = [MyPopoverController alloc] init];
popoverController.delegate = self;
Also, you'll have to implement the protocol's method:
/*
** inside MainViewController.m
*/
- (void)valueChanged:(NSString*) newVal
{
// process the string and display it where you need it
}