I find it very tedious having to create connections and other repeatable stuff in XCode 4. This just does not seem RAD to me. Let me explain
I have a View based iPad project with a single view.
On the view are about 50 buttons and 50 UIImage objects - total 100 objects. All the UIImage objects have the same image. All the buttons have the same Action. The tag of each button determines the code to run in the Action.
What I find tedious is:
I have to type in a "tag" for each of the objects - 100 tags
I have to drag each object to make an outlet which produces code like this in the .h file:
#import <UIKit/UIKit.h>
#interface bbTestViewController : UIViewController {
UIImageView *pop1;
UIImageView *pop2;
UIImageView *pop3;
UIImageView *pop4;
UIImageView *pop5;
etc....
for the 50 controls and also this code below:
#property (nonatomic, retain) IBOutlet UIImageView *pop1;
#property (nonatomic, retain) IBOutlet UIImageView *pop2;
#property (nonatomic, retain) IBOutlet UIImageView *pop3;
#property (nonatomic, retain) IBOutlet UIImageView *pop4;
#property (nonatomic, retain) IBOutlet UIImageView *pop5;
etc....
for the 50 controls.
and in the .m file:
#synthesize pop1;
#synthesize pop2;
#synthesize pop3;
#synthesize pop4;
#synthesize pop5;
etc.....
This also adds lines like this:
- (void)viewDidUnload
{
[self setPop1:nil];
[self setPop2:nil];
[self setPop3:nil];
[self setPop4:nil];
[self setPop5:nil];
etc.....
and
- (void)dealloc {
[pop1 release];
[pop2 release];
[pop3 release];
[pop4 release];
[pop5 release];
etc.....
3.Then for each of the 50 buttons I have to drag them to the the same Action to make the connection, select the touch action, type a name and then click OK.
This I find tedious and counter productive. Not RAD at all. More SAD. So much of repeating the same actions, it is painful.
So the Question is, is there an easier way. I do not believe in Copy/Paste so that solution is a no-no.
In other languages I would use a For...Each to iterate thru every object and do the needful.
e.g. for the tag:
I would have code to set the tags like this:
Integer n = 1;
For Each oObject in myView
oObject.tag = n
n = n+1
end for
So can I use code to get around these tedious processes of
tag
Declaring each object and adding the #property... code
adding the #synthesize... code
setting each object to nil
releasing each object
Somehow automate the connection from all the buttons to the Action.
I want to be able to do all this in code and without all that copy/paste stuff or those repeatable actions in IB for XCode 4 by dragging to make a connection.
Is this at all possible in this MVC system that XCode 4 uses? If so, how?
Update
After some research I found at least one answer to my Q - with regard to Connections to Actions.
It appears that if you add an object - say a button and make a connection to an action in IB, then if you copy and paste that button (or any such object), IB RETAINS the connections made so in my case above, I create the button and make the connection to the Action and if I copy and paste it, IB will make the connections to the same Action for every pasted button. At least that will save some time. HTH others.
You are right about one thing IB sucks royally. I had high hopes that Apple would have done more to fix it in XCode 4.x.
However I do wonder about your interface. Having 50 buttons on one view seems to be a little much. So you might want to refactor that aspect of your project.
As to your current issue, do realize that you can build an app without IB. You do need to learn how to programiclly build the controls and do the layout. I'd suggest looking at sample code from Apple first.
Still I'd suggest thinking long and hard about those 50 buttons.
In the view controller do:
for (int i = 0; i < 50; i++) {
UIButton *button = [UIButton buttonWithType: UIButtonTypeRoundedRect];
xpos = ...; // appropriate coordinate go here
ypos = ...; // appropriate coordinate go here
[button setFrame: CGRectMake(xpos, xpos, buttonWidth, buttonHeight)];
[button addTarget: self action: #selector(myCoolAction:) forControlEvents: UIControlEventTouchUpInside];
[button setTag: i];
[[self view] addSubview: button];
}
and you should be fine. Same for the images...
there is a sort of halfway house approach that i have used in one of my apps, where i wanted to be able to address buttons in a grid using an array in code, and like you, i wanted to design the look and feel in IB.
i did what you did in the sense that i put all the buttons on the view, and went as far as declaring an ivar, #property and #synhesize declaration for each one. since they were in a grid like you have described, what i did was name each one along the lines of button_1x1 button_1x2 etc (ie button_rxc as row x column if you think of a spreadsheet. the names are not important as you are only ever going to refer to them once in a method you call from viewDidLoad.
in that method, i created a bunch of NSArrays that held each button. for example:
landscapeHidden = [[NSArray arrayWithObjects:
button_3x1, button_3x2,
button_4x1, button_4x2,
nil] retain];
portraitHidden = [[NSArray arrayWithObjects:
button_1x3, button_1x4,
button_2x3, button_2x4,
nil] retain];
landscapeButtons = [[NSArray arrayWithObjects:
button_1x1, button_1x2, button_1x3, button_1x4,
button_2x1, button_2x2, button_2x3, button_2x4,
nil] retain];
portraitButtons = [[NSArray arrayWithObjects:
button_1x1, button_1x2,
button_2x1, button_2x2,
button_3x1, button_3x2,
button_4x1, button_4x2,
nil] retain];
i then used these arrays whenever i wanted to iterate them programatically.
the _hidden arrays are just used to indicate what buttons to hide when the display changes perspective - rather than move them around i figured it was faster to just hide the few that did not fit, and repaint them all.
as for interface builder, try this:
hold down control and click "File's Owner" once, and wait.
you will then get a large panel that you can use to wire connections to the view objects. i prefer using this method regardless of which direction i am connecting things up - ie if you drag from the objects directly the direction you drag in is not always convenient. this way you can easily view all the outlets at once. much easier from my perspective.
having said that it's pretty easy to loop through an array and assign targets or add gesture recognizers/ update titles/ colors/ whatever you like. i suppose you could also store them in a dictionary, (or have an array of dictionaries).
i went the array of having an array of dictionaries and the arrays of buttons you see above. on interface flips, the dictionaries don't change, but the buttons in the arrays get re-tagged, so any methods that have them as a "sender" can immediately get their dictionary data by indexing into the data array with
[myArray objectForIndex: ((UIButton*)sender).tag];
another trick - cmd-b build before you try doing anything in IB. this ensures it picks up things you have just edited, or files you have just added. sometimes i have had to clean/build to force an image to appear in the IB drop-downs. it's usually pretty good, but if you rename an image file, it's usually easier to delete it from the project and drag it in again, build (or clean build) then it should be good to go.
Related
I am using storyboard and kal calendar controller, I want to customize the event of choosing one day on the calendar. By default when you choose one day events on that day appears in the table view under the month calendar. What i am trying to do is when choosing one day another view controller is showing up and it is filtered on the day chosen.
Until now I founded where I should edit. But I can't call the other view to show up !!
I tried this but it dose not work !!
tableViewController *tbl = [[tableViewController alloc] initWithNibName:#"menuView" bundle:nil];
[self dismissViewControllerAnimated:YES completion:nil];
I found this but I do not know how to use it and if it is helpful for me or not .. any help ?
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"menuView" bundle:nil];
tableViewController *vc = [sb instantiateViewControllerWithIdentifier:#"menuView"];
The second batch of code in your question is headed in the right direction, but not exactly right.
First, you want to get an instance of your project's storyboard. That's what you're attempting to do in the first line of code. However, I believe you're referring to your storyboard by the wrong name. Usually Xcode defaults the name of storyboards to 'MainStoryboard.storyboard', while you're trying to refer to it as 'menuView'. So you'll want to change that first line of code to this:
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
Next, you need a way to refer to the ViewController you want in the Storyboard. That's what you're attempting in the second line of code. You need to make sure you're getting a proper reference to it:
In your storyboard, select the ViewController you're wanting to show up
In the Identity inspector, enter "tableViewController" in the Class field
Enter "menuView" in the Storyboard ID field
Finally, you'll need to add one line of code to actually present the ViewController.
So all in all your code should look something like this:
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
tableViewController *vc = [sb instantiateViewControllerWithIdentifier:#"menuView"];
[self presentViewController:vc animated:YES completion:nil];
My problem is with an iOS 6 tabbed application. My work-in-progress has 5 tabs and several tabs are gateways to other view controllers. Most pages need access to a Model object, which contains data stored as arrays, strings, and so on. A bare-bones model is populated at runtime and the user can add to it throughout the application lifespan. For example, the code listed below is from my AppDelegate file , where it is passing a pointer to the bare-bones Model to the Project View Controller. This works fine: the tab application uses the navigation controller array stack; because I know the Project page is at index 2, I can send the model to it.
My problem has to do with the sub views of the Project page. For example, as a sub view to the Project page there is (or should be) a File_IO page where the user handles file operations. The File_IO page also needs access to the Model object. But when I try to send the Model pointer from the Project page to the File_IO page, the technique I used previously (from the AppDelegate to the Project) does not work. I point to an empty Model in the FileIO ViewController.
Example code: this is in the AppDelegate, and it works fine: the bare-bones Model in the Project ViewController is populated with the data.
//To the Project View Controller...
UINavigationController *project_NavigationController =
[[tabBarController viewControllers] objectAtIndex:2];
Project_ViewController *project_ViewController =
[[project_NavigationController viewControllers] objectAtIndex:0];
//This hides the navigation bar on the project page but it also removes the bar on any subsequent pages! They need to be programmmatically reset in ViewDidLoad.
[project_NavigationController setNavigationBarHidden:YES animated:YES];
project_ViewController.currentGeoModel = newGeoModel;
Now, my Project_ViewController is embedded in a NavigationController and has 4 child ViweControllers. One is named FileIO_ViewController. I want to pass the currentModel from the Project_ViewController to the FileIO_ViewController. Under the - (void)viewDidLoad method I have tried a number of things, which do not work. For example,
UINavigationController *project_NavigationController = (UINavigationController *) self.presentedViewController;
FileIO_ViewController *fileIO_ViewController = [[project_NavigationController viewControllers] objectAtIndex:1];
fileIO_ViewController.currentModel = currentModel;
compiles but when I try to access currentModel inside the FileIO_ViewController methods, it is empty.
If anyone can take the time to help I would be very appreciative. The best answer for me would be in the form of an explicit code example showing how to pass the pointer to an object like my Model from a ViewController to another ViewController where you do not explicitly know where in the stack the child VC lies. (In my example I used Index 1 but I do not actually know at which Index the FileIO_ViewController lives as I have three other ViewControllers under the Project_ViewController. I've tried several integers with no success.)
If you do answer this, please consider me a New Guy when it comes to iOS 6 and objective C -- climbing Mount Apple has been a long haul and I isn't anywhere near the top yet!
Thanks
Tim Redfield
Norway
If you have a shared single model for your app, you shouldn't proactively pass the pointer around, you should make the model available from a single location and leave it to individual objects to access this same model when they need to. A good location for your model pointer is in your Application delegate.
In the appDelegate's .h file, declare a property for your model:
//appDelegate.h
#property (nonatomic, strong) MyAppModel* appModel;
After you instantiate your model in the appDelegate, just assign it to the property:
//appDelegate.m
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.appModel = [[MyAppModel alloc] init];
//set up bare-bones appModel here
return YES;
}
Then you can access this property from any viewController that needs model data thus:
#import appDelegate.h;
AppDelegate* appDelegate = [UIApplication sharedApplication] delegate;
MyModel* model = appDelegate.model
(Better still, make the model object into it's own Singleton object, but using the appDelegate will get you started)
If you need to pass models around, then you need to take care that you are passing them to the right object. This is where your existing code is breaking. For example you are making many assumptions about the structure of a NavigationController stack. But whenever you move back down a stack by popping a controller off the top, you lose that top controller instance completely. When you 'return' to that controller by going forwards on the stack, in fact a new instance is created (unless you have taken care to keep a pointer hanging around and make sure to push to that pointer). If you need more help on this aspect, you will need to describe exactly the layout of your app, and how you are creating your viewControllers, navigationController stack, etc.
update (following your comments)
If your changes to the model are valid throughout the app, then I don't see why you can't just access the model when you need to from wherever you happen to be in the app via appDelegate.model. If you have concurrent versions of the model, you could look at making a singleton data manager object which could handle the details of storing an array of models (or a model history) and providing the correct model as per request. The principle is the same - rather than proactively passing model objects into your viewControllers, let them request data from a centralised source as they need it.
Regarding your description "Now, my Project_ViewController is embedded in a NavigationController and has 4 child ViewControllers.", I don't think you have quite grasped the distinction between Classes, Storyboard scenes, and instances.
You have this arrangement in a part of your storyboard:
UINavigationController
| push push push
|->UIViewController1 -----> UIViewController2 -----> UIViewController3 ----->
segue segue segue
You talk about passing data directly from VC1 (say) to VC3 by accessing the NavController's stack.
What you need to consider is that the storyboard describes a template showing how instances will interrelate when they are instantiated. They are not instantiated just because the storyboard is loaded. When you have arrived at VC1, the ability to segue to VC2 and VC3 is laid out before you in the template, but neither VC2 nor VC3 - as instances - exist until you initiate the segue. The segue process instantiates it's destinationViewController Therefore it makes no sense to pass data from VC1 directly into VC3. When you are at VC1, the navController's stack only contains one item (a VC1 instance); when you segue to VC2, it then contains instances of VC1 and VC2, and it is only when you actually segue to VC3 that the instance is created and placed in the stack.
Stepping through your code:
UINavigationController *project_NavigationController =
(UINavigationController *) self.presentedViewController;
the presentedViewController property works with modal segues, where you have a presenting, and a presented, view Controller. NavControllers on the other hand work with push segues (as they push child viewControllers onto their viewControllers stack, which is where you can obtain pointers to them from).
In this context, self.presentedViewController is nil, which you are assigning to a new variable. The code does nothing, but the compiler won't complain.
FileIO_ViewController *fileIO_ViewController =
[[project_NavigationController viewControllers] objectAtIndex:1];
project_NavigationControlle is nil, so it's properties are all nil. fileIO_ViewController gets nil.
Likewise in the last line you are sending a message to nil, which is not an error, but faintly redundant.
I am using Xcode 4.3 and am somewhat of a nubbie in Xcode although I have been a programmer for many years.
I have a UIView called First that contains a UITable that gets its contents from files in memory. I then create a subview UIView called Second that makes changes in the data files that should change the contents of the table in First. When I remove Second and return to First, not surprising, the table is not updated. The next time First is loaded from the start, the table reflects the changes that were made.
First contains the method viewDidLoad which is used to load an NSArray from data in files with the data needed by the table. I am able to call viewDidLoad from Second but unless the table is updated from the newly changed NSArray the table will appear as it did before the changes were made.
So my problem is how to call a table method from Second so that the table in First is updated when I remove Second and return to First. I have tried calling the table method that loads the data into the table but have been unsuccessful since I get compile errors. I need to know how to call the table function in a way that will result in the table being updated when I return to first.
At least part of my problem is I don't know how the table update is called since it is not called from viewDidLoad as I might have expected.
Sorry, it is a long winded description of the problem. Much of the rest of the program that I am writing is working and I have been dealing with this issue for several months. I return every few weeks and take another stab at it but no luck so far. I really would appreciate any help you can give that will improve my understanding and fix the problem.
Here is the code after the fix: first (superview) pertinent code
-(void)viewDidLoad {
NSFileManager * fm = [[NSFileManager alloc] init];
NSError* err = nil;
NSPredicate *fltr = [NSPredicate predicateWithFormat:#"self ENDSWITH '.dir'"];
NSArray *array = [fm contentsOfDirectoryAtPath: userFolder error:&err];
self.listData = [array filteredArrayUsingPredicate:fltr];
[self.tableView reloadData]; //This does the updating must make table an IBOutlet as
[super viewDidLoad]; //described in one of the comments by m. Othman
}
Here is the code after the fix: second (subview) pertinent code. This code located
in the method where changes are made that should effect the table display
UIView * start = [self.view superview]; //this line and next two call viewDidLoad in first
UIResponder * nextResponder = [start nextResponder];
[nextResponder viewDidLoad];
Have you read the UIViewController API reference? There are more options beyond -viewDidLoad for dealing with the (re)appearance of a view...
well, UITableView Delegates called just on the loading of the view or when you ask it to reload ..
so in your viewDidLoad or any method that should changes the content of the tableview , you need to write this line
[tableview reloadData];
I hope I understand your question!!
My dilemma is as follows:
I have a custom subclass of UIImage (I've added some serialization methods i.e. initWithCoder, encodeWithCoder), and I would like to apply my custom subclass as a variable to a UIImageView, or at least a subclass of UIImageView.
As you are aware, UIImageView has a variable called "image", that is of type UIImage. I'd like to override this variable with my subclass.
My goal is to have a UIimageView that will respond to archivedDataWithRootObject without crashing with the encodeWithCoder message is sent to the variable image. If there is a smarter way to get there, I'm open to suggestions.
Thank you!
[edit] I think one possible route is through some type of casting...However:
UIImage *image = [[UIImage alloc] init];
MLImage *mlImage = [[MLImage alloc] init];
mlIamge = (MLImage*)image;
When I mouse-over mlImage after the second line executes, I can see that it is of type MLImage. However, even with the cast, after the third line executes mlImage becomes a UIImage. What do I need to do to change the type of image to MLImage?
[/edit]
Be careful with your terminology. It's not possible to override a property—the UIImageView will still have its own image property, it won't be replaced. You're trying to override the accessors (-image and -setImage:).
And you're right, casting is a better route. But your example has a problem: that cast is illegal. When you do [[UIImage alloc] init], you're creating an object of type UIImage. Nothing is going to change that, including your cast. Casting is a way of telling the compiler (in your case) "I know you think that this UIImage* is, well, a UIImage, but it's really a MLImage." In this case, that's a lie, since it really is a UIImage—you allocated it right there.
What you can do, though, is stick an actual MLImage into the image property of a UIImageView. For example:
MLImage *myImage = [[MLImage alloc] init];
myView.image = myImage;
And then somewhere else:
MLImage *myImage = (MLImage *)(myView.image);
And you'll get back your custom image object. But it has to have been an MLImage in the first place.
Maybe I should further qualify this - Is there a way to specify which direction a ComboBox will open without copying and pasting the entire ComboBox class and ripping out the code where it determines which direction it will open in...
I'm my specific case - I need it to open upwards - always.
UPDATE: You can't fix this by subclassing it because the function that handles the direction of the opening is:
private function displayDropdown(show:Boolean, trigger:Event = null):void
And that bad boy uses a fair amount of private variables which my subclass wouldn't have access to...
If you build up the Menu object yourself, you can place the menu anywhere you want by simply setting the x,y coordinates of the menu object. You'll need to calculate those coordinates, but you might be able to do this easily without subclassing ComboBox.
I am doing something similar with PopUpButton; you might find it easier to work with PopUpButton. This is based on real code from my current project:
private function initMenu(): void {
var m:Menu = new Menu();
m.dataProvider = theMenuData;
m.addEventListener(MenuEvent.ITEM_CLICK, menuClick);
m.showRoot = false;
// m.x = ... <-- probably don't need to tweak this.
// m.y = ... <-- this is really the interesting one :-)
theMenu.popUp = m;
}
<mx:PopUpButton id="theMenu" creationComplete="initMenu()" ... />
BTW, to get the PopUpButton to act more like I wanted it (always popup, no matter where the click), setting openAlways=true in the MXML works like a charm.
I doubt it - you'd need to subclass the control (which isn't that big a deal.)
Maybe you could mess with the real estate so it's placed in such a fashion (e.g. crowded into the lower right corner) that up is naturally coerced?
I would recommend checking out this post. Yes, you do have to grab the ComboBox code and modify it, but at least now you have an idea where the modifications need to go.
You could set the MaxDropDownHeight, if you set it big enough Windows will automatically set the direction upwards.
This irritated me no end. I have uploaded a solution, its a simple Class that extends the PopUpButton and removes the logic of stage bounds detection as it failed 50% of the time anyway. My code just allows you to simply specify whether you want to open the menu up or down:
http://gist.github.com/505255