I've got a simple form in my iPhone app. The form is laid out and managed via IB and the typical wiring (i.e. I am not creating this form programmatically).
One of the fields (and its associated label) should be shown only if a particular preference is set.
I could set the field and label's alpha to 0 and disable them in this case. The problem is that the fields below this now-invisible field would remain in the same place and there would be a big blank area. My goal is to have the screen look normal in either state.
Is there a way to programmatically remove (or add) UI elements and have those below shift up or down to make room? Or should I consider making a whole other NIB file for this second case? (and, if I do that, is there an easy way to share the common elements?)
Current UI with both controls shown
With Both http://img.skitch.com/20100704-bm41w6wtqkdgh1da99ihb7g32d.jpg
UI with optional control hidden via alpha == 0
Using Alpha to Hide http://img.skitch.com/20100704-q2sxrj3nf6ya68wp6ubn86n2pa.jpg
Desired UI with optional control hidden
Desired when hidden http://img.skitch.com/20100704-82r876pgctee8gb51ujg1dwj7k.jpg
When every UI element is linked to a IBOutlet pointer, e.g.
#property (nonatomic, retain) IBOutlet UITextField *field_a;
#property (nonatomic, retain) IBOutlet UITextField *field_b;
#property (nonatomic, retain) IBOutlet UITextField *field_c;
// ...
You can test each element's visibility by:
if (field_a.hidden) {
// ...
} else {
// ...
}
And move them around:
CGPoint pt = field_a.center;
pt.y -= 60;
field_a.center = pt;
Or by some animation:
CGPoint position = field_a.center;
position.y -= 60;
[UIView beginAnimations:#"MoveUp" context:NULL];
[UIView setAnimationDuration:0.5];
field_a.center = position;
[UIView commitAnimations];
To hide an element:
field_a.hidden = YES;
To show an element:
field_a.hidden = NO;
Use the cocoa touch properties:
.hidden 1
.userInteractionEnabled 0
Or you could:
.alpha = 0
I've seen a tutorial about this recently that involved moving a subview further down in the main view when a segmented control was selected. I believe it was an animation triggered by a beginAnimations:context:, but I can't find a reference to that tutorial right now.
Essentially, there were views under a view that were hidden, and one set was moved out of the way and the other controls unhidden.
Related
My end goal is to draw a report that looks like MS Word using Quartz into a UIView, having and then having that UIView in a UIScrollView.
What I have now is a top level Report Object, and then it's children are parts of the report to draw like ReportTitle, ReportTable, ReportSplitColumn, etc. The top level Report Object looks like:
#property (nonatomic, retain) UIFont *font;
#property (nonatomic, assign) CGFloat margins;
#property (nonatomic, assign) TextObjectStyle style;
#property (nonatomic, assign) CGColorRef color;
- (CGSize)size;
In my proof of concept, I created a ReportView object that knows how to draw ReportObjects that take a specific ReportObject, and draws it at a specific point. So my drawRect would like something like:
//pseudocode
drawRect: {
//get the current context
CGFloat startHeight = 0;
CGPoint point = CGPointMake(0, startHeight);
startHeight += [self drawReportTitle:currentContext atPoint:CGPointMake(0, point.y)].height;
point.y = startHeight;
startHeight += [self drawParagraph:currentContext withText:paragraphs atPoint:CGPointMake(0, point.y)].height;
point.y = startHeight;
}
What I started trying to do was have my ReportView have a property of NSArray *ReportObjectsArray and then have my caller class set that value, and then have my ReportView draw it. I was hoping I could do something like:
for (ReportView *r in self.ReportObjectsArray) {
startHeight += [self drawReportObject].height atPoint:CGPointMake(0, point.y;
point.y = startHeight
}
Except the problem I realize is, my own classes don't know how to draw themselves. I think I remember seeing this pattern in a Java book where the class knows how to draw itself. And that does make sense to me. However the problem I ran into was in the end, I want one UIView of type ReportView that is scrollable in UIScrollView. I wasn't really sure if each ReportObject should subclass UIView instead and then add each of these objects to the UIView in the scrollView as a subView, and then have it draw itself that way. Is that the better way to go? I'm not really sure. If it's not, what is my alternative? Would I have all my classes have a draw method, but in it, it just posts a notification and then in my ReportView class, I can have it listen to those notifications, and then call the appropriate draw methods? Thanks.
You definitely want to use a hierarchy of UIViews rather than implementing a lot drawRects. UIKit is very good about optimization and you defeat that if you do a lot of non-optimzed/naive drawRects.
You might or might not create custom UIView subsclasses for all your objects. It may be enough to use the builtin UIView subclasses and just create them based on your report objects. Subclassing is more complicated but is generally needed if you need to do more custom layout (in layoutSubviews) than the standard struts and springs provide.
I have differents UIViewController that contains components that appear and disappear from the view (with animation like translation movment).
I would like to have a single class that reduce the amount of code in each UIViewController and that can be configured in XIB files.
So my XIB file contains :
The component that need to move between two location (it's "My View" in the following screenshot)
vVisible : A UIView that acts like an indicator to represent the visible location
vHidden : A UIView that acts like an indicator to represent the hidden location
Here is what it looks like in XCode :
And I create a AppearingController (which is also a UIViewController). It controls the animation that make the component appear or disappear. It has 3 IBOutlet properties :
IBOutlet UIView * vVisible;
IBOutlet UIView * vHidden;
IBOutlet UIView * v;
And a public method (just that run the animation between the rect of the vVisible and the rect of the vHidden views) :
-(void) showVisible:(BOOL)visible {
CGRect frameDst = self.vVisible.frame;
if(!self.visible) frameDst = self.vHidden.frame;
CGPoint p = CGPointMake(CGRectGetMidX(frameDst), CGRectGetMidY(frameDst));
self.currentPosition = p;
CABasicAnimation *frameTranslation = [CABasicAnimation animationWithKeyPath:#"position"];
frameTranslation.toValue = [NSValue valueWithCGPoint:p];
frameTranslation.duration = 0.40;
frameTranslation.repeatCount = 1;
frameTranslation.removedOnCompletion = NO;
frameTranslation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
frameTranslation.cumulative = NO;
frameTranslation.additive = NO;
frameTranslation.delegate = self;
[self.v addAnimation:frameTranslation forKey:#"position"];
}
So, in my UIViewController code, I just need a few code :
In the header file : the iboutlet/property to bind to the AppearingController instance of the XIB.
In the implementation file : i can call [self.appearingController showVisible:FALSE] to run the animation to hide/unhide the component
And ... it works.
But I'm not quite satisfied because, I feel like it's a bit twisted and heavy. Because :
The AppearingController is a UIViewController (I did not manage with object or external object from the objects library).
vVisible and vHidden are views but I don't need view method/functionalities. I just need the view.frame to indicate the location.
This approach seems is good way to you ?
How can you improve it ?
Do you have some pattern you use to achieve this same goal ?
I'm interested
Thanks.
This seems fairly good ! You can code this in a number of ways actually. But when it comes to this specific example, it does not matter if you want a better approach in coding. When you take up a much much bigger project it is necessary that you design your data structures and coding scheme such that you get it done in the most time constraint and optimized manner!
For example, In a much bigger project where you need to handle 100 images , you could just create a class for animation and subclass them accordingly and either reference them or inherit them so that your approach is logical and time-saving. Hope you got the point !
I'm having kind of a dumb problem.
I am using the below code to create/modify a UILabel via code. The reason I am creating it via code is because I need it to be rotated 90 degrees and Im not aware of a way to do that in IB.
What's happening - A user hits a button that makes the text they selected to appear in the UILabel. Then when they select the button again, with different text, the new text appears in place of the old text.
The first time I hit the button, it works perfectly, but the second time I hit the button, the new label appears over the old label and the old label never disappears. I have tried removing the first label, making it nil, just removing the text, but I cannot access any part of the label once it has been created.
ViewController.h
...
UIView *viewForLabels;
UILabel *tab1Label;
}
#property (nonatomic, retain) IBOutlet UIView *viewForLabels;
#property (nonatomic, retain) IBOutlet UILabel *tab1Label
...
#end
ViewController.m
...
#synthesize tab1Label;
...
UILabel *tab1Label = [[UILabel alloc]init];
tab1Label.text = [theText];
tab1Label.backgroundColor = [UIColor clearColor];
tab1Label.textColor = [UIColor blackColor];
tab1Label.opaque = NO;
tab1Label.font = [UIFont systemFontOfSize:14];
tab1Label.numberOfLines = 2;
tab1Label.adjustsFontSizeToFitWidth=YES;
tab1Label.transform = CGAffineTransformMakeRotation (90*3.1459565) / 180);
tab1Label.frame = CGRectMake(2,87,45,119);
[viewForLabels: addSubview: tab1Label];
...
First in your code example you alloc tabLabel1 and then run a bunch of property updates against a different named object tab1Label.
Sorry if I am misunderstanding the question but why are you creating a second label? Per this part of your description:
What's happening - A user hits a
button that makes the text they
selected to appear in the UILabel.
Then when they select the button
again, with different text, the new
text appears in place of the old text.
Just update the .text property and any sizing needed why use a whole separate object?
It is ok to set the max & min value for a UISlider control in iPhone. Accordingly slider will work - OK - Good.
First, I will give an example of what exactly I want to implement.
In iPad comes with Calendar application as a default.
Open calendar, at the bottom - you can see day/week/month listing
When you slide your finger on it, you will see a pop up menu when sliding.
I want to implement the same functionality on slider.
I am trying hard, But if I am able to get x co-ordinate, I can easily implement it. Any other suggestion for implementing same are most most welcome.
Start out with a UILabel for testing (just write something in it) and add IBOulets to it, as well as the UISlider. Next, add a IBAction for "value changed" for the slider.
You then need to calculate two values to figure out where your label should be placed -- adjustment from left, and pixels per value for the slider. Your #interface might look like this:
#interface sliderViewController : UIViewController {
IBOutlet UISlider *slider;
IBOutlet UILabel *label;
float pixelsPerValue;
float leftAdjust;
}
-(IBAction) sliderValueChanged:(id)sender;
#end
In e.g. viewDidLoad: for your view controller, you do the calculation for the two floats.
- (void)viewDidLoad {
float width = slider.frame.size.width;
pixelsPerValue = width / (slider.maximumValue - slider.minimumValue);
leftAdjust = slider.frame.origin.x;
[super viewDidLoad];
}
And finally, your IBAction might look like this:
-(IBAction) sliderValueChanged:(id)sender
{
NSLog(#"changed");
CGRect frame = label.frame;
frame.origin.x = leftAdjust + (pixelsPerValue * slider.value);
label.frame = frame;
}
Hope that helps.
I don't think that thing is implemented with a UISlider, but you can use the x-coordinate of the touch easily.
In the UISlider's action, you could accept a 2nd argument.
-(void)action:(UISlider*)sender forEvent:(UIEvent*)event
// ^^^^^^^^^^^^^^^^^^^^^^^^
From the UIEvent you can get a UITouch which contains its x, y coordinates.
So after finally learning how to store user input into a variable, I am realizing it is rather difficult to have the user input numbers with decimals. The Number KeyPad doesn't allow a decimal part, and if I use Numbers & Punctuation it looks a bit weird.
So do I have to use a number Picker to smoothly allow users to input numbers? OR should I just use the Numbers & Punctuation since I was able to learn how to store that input (almost)?
Any help would be greatly appreciated.
You could always follow the same philosophy used for a Done button on the number keypad. Basically, you make your own decimal point button in that empty space in the lower-left.
Just follow the directions for adding and monitoring the button. The only thing you would need to change would be #selector(doneButton:) to #selector(decimalButton:). Your method for the decimal button would be:
- (void)decimalButton: (id) sender {
myTextField.text = [myTextField.text stringByAppendingString:#"."];
}
For my app I rolled my own number keypad. Created a modal view with all the buttons I needed, and when my UITextfield subclass becomes first responder I animate it in.
Quite a bit of work, but you can also customise the look & feel (with your own nice buttons etc) which is what I wanted for my app.
Update: My DecimalKeyboardViewController:
I actually use a subclass of UIControl instead of a UITextfield because I display it in some custom way. If I was to subclass UITextfield I'd do something like this:
#interface DecimalNumberTextfield : UITextfield {
NSDecimalNumber *value;
}
#property (nonatomic, retain) NSDecimalNumber *value
#end
#implementation DecimalNumberTextfield
- (BOOL)becomeFirstResponder
{
if ([super becomeFirstResponder]) {
[[DecimalKeyboardViewController sharedDecimalKeyboardViewController] showForField:self];
return YES;
} else {
return NO;
}
}
- (BOOL)resignFirstResponder
{
if ([super resignFirstResponder]) {
[[DecimalKeyboardViewController sharedDecimalKeyboardViewController] hide];
return YES;
} else {
return NO;
}
}
- (void)setValue:(NSDecimalNumber *)aValue {
// you need to set value and update the displayed text here
}
#end
Here is the DecimalKeyboardViewController. You'll need to create the nib file and add tags to the buttons (0-9 for the numbers probably, and other tags for the other buttons that you'd use), then hook up the buttons to -buttonPressed::
#interface DecimalKeyboardViewController : UIViewController {
DecimalNumberTextField *fieldBeingEdited;
}
#property (nonatomic, retain) DecimalNumberTextField *fieldBeingEdited;
+ (id)sharedDecimalKeyboardViewController;
- (void)showForField:(DecimalNumberTextField *)aTextField;
- (void)hide;
- (IBAction)buttonPressed:(id)sender;
#end
#implementation DecimalKeyboardViewController
static DecimalKeyboardViewController *sharedViewController;
+ (id)sharedDecimalKeyboardViewController
{
if (!sharedViewController) {
sharedViewController = [[DecimalKeyboardViewController alloc]
initWithNibName:#"DecimalKeyboardViewController"
bundle:[NSBundle mainBundle]];
}
return sharedViewController;
}
- (void)showForField:(DecimalNumberTextField *)aTextField
{
self.fieldBeingEdited = aTextField;
// add ourselves to the window unless we're there already
UIWindow *theWindow = self.fieldBeingEdited.window;
CGRect frame = self.view.frame;
if (!self.view.superview) { // add ourselves to the UIWindow unless we're already visible
frame.origin.y = theWindow.frame.size.height; // we start just out of sight at the bottom
self.view.frame = frame;
[theWindow addSubview:self.view];
}
// start animating
[UIView beginAnimations:nil context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
// animate the keyboard in
frame.origin.y = theWindow.frame.size.height - frame.size.height;
self.view.frame = frame;
// GO!
[UIView commitAnimations];
}
- (void)hide
{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
if (self.view.superview) {
CGRect frame = self.view.frame;
frame.origin.y = self.view.superview.frame.size.height;
self.view.frame = frame;
}
[UIView commitAnimations];
self.fieldBeingEdited = nil;
}
- (IBAction)buttonPressed:(id)sender
{
UIButton *button = (UIButton *)sender;
NSDecimalNumber *oldNumber = self.fieldBeingEdited.value;
NSDecimalNumber *newNumber;
switch (button.tag) {
// calculate the new number based on what button the user pressed
}
// update the DecimalNumbertextfield
self.fieldBeingEdited.value = newNumber;
}
#end
Update 2: Since I subclassed UIControl I didn't have to deal with the standard keyboard popping up. If you subclass UITextfield I'd think the normal keyboard would be displayed as well. You'd have to prevent that somehow (not sure how) or subclass UIControl like I did.
If you don't want to go with Chris's answer, you could use numbers and punctuation keypad, and then "disable" all non digit/decimal point inputs by implementing the methods of UITextFieldDelegate
In the SDK 3.0 release notes, Apple wrote the following warning:
Don't draw custom buttons on top of existing keyboards. Any number of drawing, event, or compatibility problems could come up.
Now it's true, they have been accepting applications that add a button over an existing keyboard, however this technique uses an undocumented link to the UIKeyboard View and could easily be rejected the next time it's submitted. Technically it falls into the undocumented API realm, however I don't believe their automated tools pick it up so it's not being flagged.
You should post a bug on the Apple Developer Connection website, since they are counting the number of bug reports on this issue before they will address it formally...
There are an number of ways they could fix this, including:
1) Support more predefined keyboard options (decimal, currency, etc).
2) Expose the UIKeyboard methods
3) Provide customizing keyboard options that can be localized
If you intend to localize your App, you will have problems with different keypad sizes when you overlay a "." key on the empty space on one of the existing keypad (same problem that the "Done" button has in that approach).
I am currently using the overlayed "." in one of my own Apps, and will probably end up creating an entirely custom "decimal" keypad to replace this so I don't risk being rejected in an App update in the future.
-t