So I'm facing this problem, the standard UIPicker doesn't look good in my (proto) app and I would really change its appearance.
I've already googled it and found this useful link http://aralbalkan.com/2985. Looks good but I'm wondering if there isn't an easier way to do it.
Here on stack overflow I've found this thread Is it possible to 're-skin' the IOS Date Picker?, it's "unsolved" but pointing to the first article.
I also did a tutorial in Apress Book "Beginning Iphone development" (chapter 7 I think), very useful, but there I was changing just the content of the columns, not the full appearance of it.
Just to be clear, it would be great to have something like this:
Anyone has a good tutorial/link/suggestion for an Objective-c rookie?
Thx in advice
EDIT:
So I've implemented the method detailed in the two answers below and it works like a charm. But I'm still stuck with the background color ....
the numbers shoud be able to create via using UIPickerViewDelegate's pickerView:viewForRow:forComponent:reusingView:, while the comma could be added via addSubView:.
or you go down another road and use a open source alternative, i.e. AFPickerView
You can manipulate teh views in the hierarchy — but probably it is easier to recreate the picker …
-(void)viewDidAppear:(BOOL)animated
{
for (UIView *view in [self.pickerView subviews]) {
NSLog(#"%#", view);
}
[(UIView *)[[self.pickerView subviews] objectAtIndex:1] setBackgroundColor:[UIColor blueColor]];
[(UIView *)[[self.pickerView subviews] objectAtIndex:0] setHidden:YES];
[(UIView *)[[self.pickerView subviews] lastObject] setHidden:YES];
for (UIView *view in [(UIView *)[[self.pickerView subviews] objectAtIndex:2] subviews]) {
NSLog(#"> %#", view);
}
[[[(UIView *)[[self.pickerView subviews] objectAtIndex:2] subviews] lastObject] setHidden:YES];
}
use the delegate of the UIPickerView. Create a method called
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view
So at this point, you can create a UIView (or subclass). At this point, you have full control over what the view will display. You can change the background color and alpha. And if you subclass UIView, you can have your own drawing routines.
Related
I have a universal app, the code is the same. I have a UIScrollView in which has the scrollToTop working on the iPad but not on iPhone. I am pretty frustrated by this.
I know there's a similar thread posted here, but that is not the case. I used to have the scrolling to work before this both on the iPad and iPhone. Any idea what to look for?
The structure of the code is like this. I have a mainVC called A. I then have a VC called B. There is also another VC called C, which has a UIScrollView. I added C as B's child view controller. and then B as A's child VC. Now the scroll view on C did not have the scrollToTop working.
The delegate scrollViewShouldScrollToTop is also called only in the iPad, not in the iPhone.
Take a look at my answer to the question you've talked about. I've just added it a moment ago.
EDIT
I don't have the original code I've made, but it should be like that:
-(void)cleanUp:(UIScrollView*)view{
if([view isKindOfClass:[UIScrollView class]]){
view.scrollsToTop = NO;
}else{
for(UIScrollView* subview in view.subviews){
if([subview isKindOfClass:[UIView class]]){
[self cleanUp:subview];
}
}
}
}
and you can call it like this:
[self cleanUp:self.view];
You may also need even more tough variant of that routine (sometimes you may have a tableView inside a scrollView or something like that):
-(void)cleanUp:(UIScrollView*)view{
if([view isKindOfClass:[UIScrollView class]]){
view.scrollsToTop = NO;
}
for(UIScrollView* subview in view.subviews){
if([subview isKindOfClass:[UIView class]]){
[self cleanUp:subview];
}
}
}
I Just took the solution of #Ariel and done some improvements I want to share with you:
+ (void)globalDisableScrollToTop:(UIView *)_view;
{
// Check whether we got a scroll view
if ([_view isKindOfClass:[UIScrollView class]])
{
// Disable scroll to top
((UIScrollView *)_view).scrollsToTop = NO;
}
// Iterate all subviews
for (UIView *view in _view.subviews)
{
// Recursive call of this method
[self globalDisableScrollToTop:view];
}
}
Or without comments:
+ (void)globalDisableScrollToTop:(UIView *)_view;
{
if ([_view isKindOfClass:[UIScrollView class]])
((UIScrollView *)_view).scrollsToTop = NO;
for (UIView *view in _view.subviews)
[self globalDisableScrollToTop:view];
}
It now fixes all subviews and could be implemented as static method (of your root scroll view class).
If possible, Please paste the add subview code here. Looks like the problem is the view on which the scrollview is present is getting hide behind other view. Try changing the sequence of adding subview. Or you may also try bringSubviewtoFront property for setting the scrollview on top.
Let me know if it helps.
I am looking for a way to automatically localize texts on buttons/textfields etc and for this method I need to find all (for example) UIButton's on a UIView.
I tried the following 2 methods, but they both do no work like I want them to work:
for (UIView* subView in self.view.subviews)
{
NSLog(#"object class : %#", [subView class]);
if ([subView isMemberOfClass:[UIButton class]])
NSLog(#"Button found!");
}
The problem with this piece of code is that a RoundedRectButton does not match the UIButton class, while it really is just a UIButton.
I also tried the following:
for (UIButton* button in self.view.subviews)
{
// Do my stuff
}
But the stupid thing is, is that cocoa-touch actually just lists all subviews in that for-loop (also the UITextFields etc).
Is there a way to actually just get all UIButtons from a view? Or do I really need to find controls by looking at their selectors.
Why write one-off code like this when you can dial up the awesomeness by adding a category method to UIView using blocks? Take a look at the code at the very bottom. Using this recursive method with blocks you can do things like disable all UITextFields in a view controller's view:
[self.view forEachUITextFieldDoBlock:^(UITextField *textField) {
textfield.enabled = NO;
}];
Or fade out all UITextFields in a view controller's view:
[UIView animateWithDuration:0.25 animations:^{
[self.view forEachUITextFieldDoBlock:^(UITextField *textField) {
textField.alpha = 0.0;
}];
}
completion:^(BOOL finished){
// nothing to do for now
}];
Blocks are pretty amazing in that you can even pass other methods inside a block. For example, the code below passes each UITextField found to my inserAdornmentImage:forTextView method, which adds a custom background image to each text view:
[self.view forEachUITextFieldDoBlock:^(UITextField *textField) {
[self insertAdornmentImage:textFieldBGImage forTextField:textField];
}];
Blocks make the method incredibly flexible so you aren't having to write a specialized method each time you want to do something new with the controls you find. Here's the magic sauce:
#implementation UIView (Helper)
- (void) forEachUITextFieldDoBlock:(void (^)(UITextField *textField))block
{
for (UIView *subview in self.subviews)
{
if ([subview isKindOfClass:[UITextField class]])
{
block((UITextField *)subview);
} else {
[subview forEachUITextFieldDoBlock:block];
}
}
}
#end
the first method is correct, except you need to change isMemberOfClass function to isKindOfClass:
isKindOfClass: Returns a Boolean value
that indicates whether the receiver is
an instance of given class or an
instance of any class that inherits
from that class.
So I'm totally stumped by this one and tempted to call "OS bug".
I have a TableView controller with a single section, and in the header for that section there is an UITextField. Several operations result in rows being added/removed without a problem. However, as soon as text is edited in the header, and the keyboard dismissed, any insertion/removal of rows results in an immediate crash.
And it can actually be simplified further - simply calling beginUpdates/endUpdates on the table once the keyboard is dismissed is enough to cause a crash. The end of the callstack is:
_CFTypeCollectionRetain
_CFBasicHashAddValue
CFDictionarySetValue
-[UITableView(_UITableViewPrivate) _updateWithItems:withOldRowData:oldRowRange:newRowRange:context:]
-[UITableView(_UITableViewPrivate) _endCellAnimationsWithContext:]
-[UITableView endUpdates]
I've put together a minimal example that demonstrates the problem.
Complete Controller source: http://www.andrewgrant.org/public/TableViewFail.txt
Example Project: http://www.andrewgrant.org/public/TableViewCrash.zip
Most relevant code:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
// create header view
UIView* header = [[[UIView alloc] initWithFrame:CGRectMake(0.f, 0.f, 320.f, 50.f)] autorelease];
// text field
UITextField* textField = [[[UITextField alloc] initWithFrame:CGRectMake(10.f, 12.f, 300.f, 28.f)] autorelease];
textField.text = #"Edit, then 'Save' will crash";
textField.borderStyle = UITextBorderStyleRoundedRect;
textField.clearButtonMode = UITextFieldViewModeAlways;
textField.delegate = self;
[header addSubview:textField];
return header;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
// no purpose, but demonstrates updates work at this point
[self.tableView beginUpdates];
[self.tableView endUpdates];
[textField resignFirstResponder];
// immediate crash
[self.tableView beginUpdates];
[self.tableView endUpdates];
return YES;
}
Just an update - I submitted a bug report and repro case to Apple and they confirmed it's a bug in iOS 4.0. As of iOS 4.1 beta 2 it had not been fixed.
My work around was to turn the first row of my table into a pseudo header that occupies the entire content view and has a custom height. It's not quite as good (things cant reach to the edge of the screen for example) but it's close and doesn't crash.
I hit this bug last night and spent a few hours this morning trying to figure it out. The other answers in this thread didn't work for me, but did help me come up with a workaround that I think is best.
Cameron suggested making an offscreen UITextField the firstResponder, then resigning it before calling endUpdates on the tableview. This didn't work for me, but it gave me an idea.
In the context of my custom header view, I re-parent the text field (in my case, a UISearchBar, actually) before calling resignFirstResponder. Then I put it back:
[self.window addSubview: sb];
[sb resignFirstResponder];
[self addSubview: sb];
a few lines later, when I call [tableView endUpdates], it no longer crashes.
Edit: It just got a bit more complicated. The problem is that if first-responder status is otherwise revoked (for example, the user dismisses the keyboard), this parent-swapping code isn't executed, and we'll eventually get the crash. My current fix is to place a category-override on UITextField resignFirstResponder -- seems to work but not sure yet if there are any adverse side effects.
#implementation UITextField (private)
- (BOOL) resignFirstResponder
{
UIView* superviewSave = self.superview;
[self.window addSubview: self];
BOOL success = [super resignFirstResponder];
[superviewSave addSubview: self];
return success;
}
#end
Taking Tom's solution one step further, I noticed this solution only works on iOS 4.X, which is ok because this problem only exists in iOS 4.X. Therefore I changed his method to:
#implementation customUITextField
- (BOOL)resignFirstResponder {
if ( [[UIDevice currentDevice].systemVersion characterAtIndex:0] == '4' ) {
UIView* superviewSave = self.superview;
[self.window addSubview:self];
BOOL success = [super resignFirstResponder];
[superviewSave addSubview:self];
return success;
}
return [super resignFirstResponder];
}
#end
I came across this bug myself and am so glad I found your post because I was banging my head on my desk trying to figure out where I screwed up.
For my workaround, I created an offscreen UITextField and called becomeFirstResponder and then resignFirstResponder on that text field before doing the updates. This avoided the crash and didn't require any redesign of the headers or cells.
I have a UITextView in my Navbar that is acting as a search box. I would like to dismiss the associated keyboard when the user taps below the text box - namely on the MKMapView. However I can't figure out how to do this since it doesn't look like I can intercept touches from the mapview.
I have looked at a number of solutions, but none seem to work for my case as far as I can tell. Does anyone have a simple way to do this? I am a bit of a noob, so please let me know if I am not providing some relevant information, and please provide a few lines of example code in your answer if you can - I am still a bit shaky with terminology. Thanks!
screenshot http://img532.imageshack.us/img532/4070/keyboardsm.png
A less hacky solution is to rely on one of the MKMapView delegate methods to dismiss the keyboard
- (void) mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated {
if ([self.textField isFirstResponder]) {
[self.textField resignFirstResponder];
}
}
Assuming
UITextView *textView;
Then you can dismiss the keyBoard by sending:
[textView resignFirstResponder];
Also: you may prefer to use a UISearchBar and set it as the navigationItem.titleViewof the ViewController. This offers some nice delegate methods.
I know this is an old one but in case anyone else is looks for a simple answer to this, here's my solution to get a "mapView" to resignFirstResponder. This will work in a similar way to the Google Maps app on the iphone, where a semi transparent box appears when you start editing the search text field.
Firstly you need to make your view controller a UISearchBarDelegate
#interface ViewControllerName <UISearchBarDelegate>
// your code here
#end
Then implement the following delegate methods:
#implementation ViewControllerName
// your code here
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
darkBg = [[UIControl alloc] initWithFrame:CGRectMake(0, 244, 320, 300)];
[darkBg setBackgroundColor:[UIColor blackColor]];
[darkBg addTarget:nil action:#selector(hideKeyboard) forControlEvents:UIControlEventTouchDown];
[darkBg setAlpha:0.8];
[UIView beginAnimations:#"slideup" context:nil];
[darkBg setCenter:CGPointMake(160, 194)];
[self.view addSubview:darkBg];
[UIView commitAnimations];
}
- (void)hideKeyboard {
[UIView beginAnimations:#"fadeou" context:nil];
[darkBg setAlpha:0.0];
[UIView commitAnimations];
[addressField resignFirstResponder];
[darkBg performSelector:#selector(removeFromSuperview) withObject:nil afterDelay:1.0];
[darkBg release];
}
When the keyboard appears, I want to set the
keyboardAppearance = UIKeyboardAppearanceAlert
I've checked the documentation and it looks like you can only change the keyboardType.
Can this be done without violating any of Apple's private API's ?
The best way that I found to do this, if you want to do it throughout your app, is to use Appearance on UITextField. Put this in your AppDelegate on launch.
[[UITextField appearance] setKeyboardAppearance:UIKeyboardAppearanceDark];
This should do it:
for(UIView *subView in searchBar.subviews)
if([subView isKindOfClass: [UITextField class]])
[(UITextField *)subView setKeyboardAppearance: UIKeyboardAppearanceAlert];
didn't find any other way of doing...
This no longer works on iOS 7 because the UISearchBar view hierarchy has changed. The UITextView is now a subview of the first subview (e.g. its in the searchBar.subviews[0].subviews array).
A more future proof way to do this would be to check recursively the entire view hierarchy, and to check for UITextInputTraits protocol rather than UITextField, since that is what actually declares the method. A clean way of doing this is to use categories. First make a category on UISearchBar that adds this method:
- (void) setKeyboardAppearence: (UIKeyboardAppearance) appearence {
[(id<UITextInputTraits>) [self firstSubviewConformingToProtocol: #protocol(UITextInputTraits)] setKeyboardAppearance: appearence];
}
Then add a category on UIView that adds this method:
- (UIView *) firstSubviewConformingToProtocol: (Protocol *) pro {
for (UIView *sub in self.subviews)
if ([sub conformsToProtocol: pro])
return sub;
for (UIView *sub in self.subviews) {
UIView *ret = [sub firstSubviewConformingToProtocol: pro];
if (ret)
return ret;
}
return nil;
}
You can now set the keyboard appearance on the search bar in the same way you would a textfield:
[searchBar setKeyboardAppearence: UIKeyboardAppearanceDark];
keyboardAppearance is a property of the UITextInputTraitsProtocol, which means that the property is set via the TextField object. I'm not aware of what an Alert Keyboard is, from the SDK it's a keyboard suitable for an alert.
here's how you access the property:
UITextField *myTextField = [[UITextField alloc] init];
myTextField.keyboardAppearance = UIKeyboardAppearanceAlert;
Now when the user taps the text field and the keyboard shows up, it should be what you want.