UPDATE: I solved this using a workaround with custom views on the picker instead of strings. Still really curious why this was happening though.
Only two of our users reported this bug and so far and it's pretty strange. I am unable to replicate it on the sim or any of our test devices. Basically, for this one user, a UIPickerView displays everything very dark. Here is an image of how it's supposed to look:
Here is what it looks like on the user's phone who reported the bug:
At first glance, the bugged pickerview appears black. But if you turn up your screen brightness all the way and look really closely, you'll see that the picker view is actually there (also, the textview wouldn't have populated if it wasn't there). The picker view is displaying with an almost black background and black text.
I'm not doing anything tricky or messing with the frame of the picker view to create it or manage it:
- (UIPickerView *)pickerView
{
if (!_pickerView) {
_pickerView = [[UIPickerView alloc] init];
_pickerView.delegate = self;
_pickerView.dataSource = self;
}
return _pickerView;
}
I'm not using any custom views, just assigning strings in pickerView:titleForRow:forComponent
#pragma mark picker datasource
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return self.rowTitlesForComponent.count;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
return ((NSArray *)self.rowTitlesForComponent[component]).count;
}
#pragma mark - picker delegate
- (NSString *)pickerView:(UIPickerView *)pickerView
titleForRow:(NSInteger)row
forComponent:(NSInteger)component
{
return self.rowTitlesForComponent[component][row];
}
Here's my hunch: I'm assigning the picker view to the input view of the textfield so that it pops up as a "keyboard":
self.textField.inputView = self.pickerView;
The textview is a subview of a custom tableview cell. This custom tableview cell is a subclass of another type of tableview cell that contains textviews which pop up ordinary uikeyboards. In this superclass, I'm assigning the keyboard appearance to UIKeyboardAppearanceDark. The odd thing is that this is affecting <1% of our users, so perhaps it's some off combination of OS version and code or something?
I tried messing around with all the accessibility settings on the phone, but could not replicate.
Do you have any idea what might cause this?
Related
I'm trying to display a UIPickerView that has the selected row displayed in a unique text color. So the selected row should be displayed in a red font, and all other (unselected) rows are displayed in a black/default font.
I have implemented this:
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
UILabel *label = (UILabel*)[pickerView viewForRow:row forComponent:component];
label.textColor = [UIColor redColor];
UILabel *oldLabel = (UILabel*)[pickerView viewForRow:self.previouslySelectedRow forComponent:component];
oldLabel.textColor = [UIColor blackColor];
self.previouslySelectedRow = row;
}
Which works when changing selections flawlessly, but does not do anything for the initial row that is displayed.
I am calling:
[pickerView selectRow:initialRow inComponent:0 animated:NO];
After I display the UIPickerView, but I have realized that this does not result in didSelectRow being called (that only happens when the user changes the selection manually).
I have tried to set the UILabel textColor value myself - something like:
UILabel *label = (UILabel*)[pickerView viewForRow:startingRow forComponent:0];
label.textColor = [UIColor redColor];
But unfortunately that results in nothing happening. I call this AFTER displaying the pickerview.
Is there some easy way of doing this? Am I missing something? Thanks for any/all help!
Zach
I had this issue myself just recently working in Xamarin Monotouch (.net for iOS) I couldnt find a "clean" fix, but I made a very small hack which seems to work fine thus far. I will provide my solution in C#, apologies as I can read ObjC however I would only embarrass myself by trying to write it. You should get the idea pretty easy I hope.
Override UIPickerViewDelegate. Add an int field, with which we will populate the index of the dataitem we need to select upon the picker controls first use.
protected class PickerDelegate : UIPickerViewDelegate
{
// Contruction...overrides etc
public int SelectIndexOnLoad { get; set; }
}
Now in the area of code where you load/instantiate the pickercontrol (ViewDidLoad etc) locate the index of the datasource, which contains the data you want to display/select first in the picker.
// First get the index value of the current set of data you
// want to display, assuming here I have a list of items as my
// picker datasource
var x = this.items.IndexOf(data_value_to_display);
// Then assign it to our PickerDelegate.SelectIndexOnLoad
this.myPickerDelegate.SelectIndexOnLoad = x;
this.myPickerView.selectRow(x, 0, true);
// Note in Xamarin, the objc delegate call selectRow() is mapped to Select()
In the pickerdelegates viewForRow() call (Xamarin objC call is mapped to GetView()) we compare the PickerDelegate.SelectIndexOnLoad to the passed row variable, and decide on UI values
public override UIView viewForRow(UIPickerView pickerView, int row, int component, UIView view)
{
// Forget reusing the passed view
// there is a bug in ios 7,8,9 which ensures its ALWAYS null, nice Apple!
var view1 = new UILabel(predefined_rect_size);
if (this.SelectIndexOnLoad == row)
{
view1.BackgroundColor = UIColor.Pink;
}
else
{
view1.BackgroundColor = UIColor.White;
}
// Optionally reset the SelectIndexOnLoad so that as the user moves
// the picker (viewForRow is called constantly for redraws)
// it goes back to normal color once moved.
// If you want to keep it colored so the user knows the old original value
// Simply omit this line
this.SelectIndexOnLoad = row;
return view1;
}
This works fine, however, I dont like it. Im hoping an ObjC guru will enlighten us, but until then, this hack does the trick.
Kindest Regards
3 things to check
1. where is ‘pickerView.selectRow’ called ?
it shouldn’t be called in viewDidLoad or viewWillAppear
Instead place them in viewDidLayoutSubviews (prefer) or viewDidappear
to give some time for
pickerView(_ pickerView: UIPickerView, viewForRow row: Int ..)
to return all the UIView
(for iphone6 and later, if you place it in viewDidLayoutSubviews,
add 'self.yourPIckerView.layoutIfNeeded()' before selecting row)
2. didSelectRow should be called manually
‘pickerView.selectRow’ doesn’t automatically trigger didSelectRow.
After the line, place this to manually trigger it.
self.pickerView(self.yourPickerView, didSelectRow: row, inComponent: comp)
3. animated parameter should be ‘false’
‘pickerView.selectRow(row, inComponent: comp, animated: false)’
If it’s ’true’, it will take some time for the picker to eventually get to the row you manually selected
and color change could be ignored by the delay
The UIPickerView class has a method selectedRowInComponent that returns the selected row for the component. That may or may not be the item that is in the center of the picker view. What I'd like is to be able to tell what index of the pickerView data is currently being displayed (not selected).
To illustrate the difference, if you tap a value in the UIPickerView dial, the row for that value seems to always be returned in selectedRowInComponent. However, if the user scrolls the picker through several values and then lifts the finger, selectedRowInComponent is not updated, or it is updated to the value that just scrolled off.
Is there anyway to determine where the picker component is actually dialed?
The value is always available in method pickerView:didSelectRow:inComponent: but I would like to query it in method pickerView:titleForRow:forComponent:
Why? Because while the UIPickerView is being scrolled selectedRowInComponent is not being called. If for some reason I get to the end of the data and need to add more, I need to do it in a method that is being called. That would seem to be pickerView:titleForRow:forComponent:, which is called for every row.
I know this question is a bit old, but by answer it I could help someone else.
I think I found a solution to this problem you had.
Solution
- (NSString *)pickerView:(UIPickerView *)pickerView
titleForRow:(NSInteger)row
forComponent:(NSInteger)component
{
// Passing the row being viewed to a function that does something with it.
[self handleRowBeingViewed:[pickerView selectedRowInComponent:component]];
}
- (void)pickerView:(UIPickerView *)pickerView
didSelectRow:(NSInteger)row
inComponent:(NSInteger)component
{
// Passing the row being viewed to a function that does something with it.
[self handleRowBeingViewed:[pickerView selectedRowInComponent:component]];
}
/**
* It receives, in input 'rowBeingViewed', the row being viewed by the user in the center of the picker view, and use it to XYZW...
*
*
* #param rowBeingViewed Row currently being viewed by the user in the center of the picker view.
* #return
*/
- (void) handleRowBeingViewed:(NSInteger)rowBeingViewed
{
// Printing for debugging.
NSLog(#"String Being Viewed: %#", pickerViewArray[rowBeingViewed]);
// DO YOUR STUFF HERE
// ...
}
Explanation
The UIPickerView allows you to know which row is selected (or viewed in the center, which I think is the same) at any time by using the method selectedRowInComponent.
If you use that method selectedRowInComponent inside the delegate's method pickerView:titleForRow:forComponent, you can know which row is being viewed in the center every time a new title is being "printed" to the user in the picker view.
Sometimes, if in the Picker View you scroll only 1 or 2 rows, the delegate's method pickerView:titleForRow:forComponent does not get called and you won't know which row is being viewed in the center. To solve that, also use the same method selectedRowInComponent inside the delegate's method pickerView:didSelectRow:inComponent:.
Hope this helps someone.
I have multiple textfields on my view, and each time a textfield is taped a picker appears(the picker was made by code). This works fine for the first textfield, I can also select a row and the textfield receives it. But when I tap the second one and the picker appears, the row selected appears on the first textfield. I know I can use "tag" to distinguish them, but then I get stuck...it doesn´t work.
Does anyone have/has this problem? What method do I have to change?
try creating one picker for each textfield pick1,pick2.......then in the
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
method u check
if( pickerview==pick1)
{
text1.text= (pick1 value at row);
}
else if(pickerview==pick2)
{
text2.text= (pick2 value at row);
}
Im using a uiPickerView that haves images instead of strings for the selections, is working ok for showing the images, I got inspiration from the apple UIcatalog example on how to put the images,
Im implementing it programatically no IB,
So the question is, how to change an image according to the selected cell?
i have
* MainVC (my main view controller)
* CustomView (defining picker size)
* CustomPickerDataSource (the data source for the picker :)
in CustomPickerDataSource, send the data for wich row was selected
- (void)pickerView:(UIPickerView *)pickerView didSelectRow: (NSInteger)row inComponent:(NSInteger)component {
// Handle the selection
NSLog(#"seleccionokkkk: %d", row);
[MainVC entro:row];
}
and in my MainVC
+(void) entro: (int) cuca {
NSLog(#"sumbalo :%d", cuca);
}
I get the number of cell selected on my MainVC, which is where I want to show the different images,
but when selecting the image in my +(void) entro: (int) cuca
I get warnings of course as Im setting instance variables with a class method,
so how can I show the image according to which cell number I receive?
Pseudocode:
if image == 0, show image0
Where to put my image showing code?, and how to set the variable for the incoming message?
thanks a lot!
I'd assume you have as many images as you have items in the picker. Put them into an array in the same order as you did in the picker view. Then take the UIImageView that you want to change to the selected picture, and set it to the picture in the array with the given index.
You can do this in your entro: method (which should be an instance method, no reason that it shouldn't or can't be), or you can just go ahead and do it in the pickerView:didSelectRow: method.
You can set images like this:
UIImageView *myImageView;
NSArray *myImageArray;
...
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:
(NSInteger)row inComponent:(NSInteger)component {
[myImageView setImage:[myImageArray objectAtIndex:row]];
}
Is there a way (either in IB or code) to set the tab order between text fields in a view?
Note that I'm not talking about the next form field after the return (or "Next") button is pressed -- many bluetooth keyboards have a tab key, which seems to cycle through the fields in completely different order. In my particular case, this order doesn't correspond to the fields' position in the view or even the order in which the fields were added. Modifying the xib file by hand to change the NSNextKeyView doesn't seem to make a difference either.
Does anyone know how to change this order?
#sprocket's answer was only somewhat helpful. Just because something works out of the box doesn't mean you should stop thinking about a better way -- or even the right way -- of doing something. As he noticed the behavior is undocumented but fits our needs most of the time.
This wasn't enough for me though. Think of a RTL language and tabs would still tab left-to-right, not to mention the behavior is entirely different from simulator to device (device doesn't focus the first input upon tab). Most importantly though, Apple's undocumented implementation seems to only consider views currently installed in the view hierarchy.
Think of a form in form of (no pun intended) a table view. Each cell holds a single control, hence not all form elements may be visible at the same time. Apple would just cycle back up once you reached the bottommost (on screen!) control, instead of scrolling further down. This behavior is most definitely not what we desire.
So here's what I've come up with. Your form should be managed by a view controller, and view controllers are part of the responder chain. So you're perfectly free to implement the following methods:
#pragma mark - Key Commands
- (NSArray *)keyCommands
{
static NSArray *commands;
static dispatch_once_t once;
dispatch_once(&once, ^{
UIKeyCommand *const forward = [UIKeyCommand keyCommandWithInput:#"\t" modifierFlags:0 action:#selector(tabForward:)];
UIKeyCommand *const backward = [UIKeyCommand keyCommandWithInput:#"\t" modifierFlags:UIKeyModifierShift action:#selector(tabBackward:)];
commands = #[forward, backward];
});
return commands;
}
- (void)tabForward:(UIKeyCommand *)command
{
NSArray *const controls = self.controls;
UIResponder *firstResponder = nil;
for (UIResponder *const responder in controls) {
if (firstResponder != nil && responder.canBecomeFirstResponder) {
[responder becomeFirstResponder]; return;
}
else if (responder.isFirstResponder) {
firstResponder = responder;
}
}
[controls.firstObject becomeFirstResponder];
}
- (void)tabBackward:(UIKeyCommand *)command
{
NSArray *const controls = self.controls;
UIResponder *firstResponder = nil;
for (UIResponder *const responder in controls.reverseObjectEnumerator) {
if (firstResponder != nil && responder.canBecomeFirstResponder) {
[responder becomeFirstResponder]; return;
}
else if (responder.isFirstResponder) {
firstResponder = responder;
}
}
[controls.lastObject becomeFirstResponder];
}
Additional logic for scrolling offscreen responders visible beforehand may apply.
Another advantage of this approach is that you don't need to subclass all kinds of controls you may want to display (like UITextFields) but can instead manage the logic at controller level, where, let's be honest, is the right place to do so.
I'm interested in solving the same problem, although so far the default order, which appears to be left to right, then top to bottom, is the one I want.
I tested the hypothesis that the cursor moves in depth-first order through the tree of subviews and superview, but that is not true. Changing the order of subviews without changing their location didn't change the order of fields traversed by tab presses.
One possibly useful feature is that the text field delegate's textFieldShouldBeginEditing method appears to be called for every text field in the application's window. If that returns NO, then the text field won't be chosen, so if you can define your desired order and make only the right one return YES, that might solve your problem.
This is how you set the tab order on iOS:
http://weaklyreferenced.wordpress.com/2012/11/13/responding-to-the-tab-and-shift-tab-keys-on-ios-5-ios-6-with-an-external-keyboard/
The Tab key behaviour in ios will be as follows:-
when u press tab on external keyboard- the control traverses across all the textfields in that screen by calling only shouldBeginEditing method where its return value is also determined by Apple which cant be override.
After scanning all the fields it calculates nearest x positioned Textfield relative to view offset from the current Textfield and then nearest Y Positioned Field.
Also can't be done anything until control comes to textFieldDidBeginEditing method.
Reason for apple's restriction might be to let devs to follow the guidelines of UI where next responder of field should be it's closest positioned Field rather than any other field .
Register a UIKeyCommand to detect the tab key pressed. I did this in my current view controller.
self.addKeyCommand(UIKeyCommand(input: "\t", modifierFlags: [], action: #selector(tabKeyPressed)))
Inside the key tabKeyPressed handler find your current active field then set your next responder. orderedTextFields is an array of UITextField in the tab order I want.
func tabKeyPressed(){
let activeField = getActiveField()
if(activeField == nil){
return
}
let nextResponder = getNextTextField(activeField!)
nextResponder?.becomeFirstResponder()
}
func getActiveField() -> UITextField? {
for textField in orderedTextFields {
if(textField.isFirstResponder()){
return textField
}
}
return nil
}
func getNextTextField(current: UITextField) -> UITextField? {
let index = orderedTextField.indexOf(current)
if(orderedTextField.count-1 <= index!){
return nil
}
return orderedTextField[index! + 1]
}
You can do this by setting the tag for each textfield and handling this in the textfieldShouldReturn method.
See this blogpost about it:
http://iphoneincubator.com/blog/windows-views/how-to-create-a-data-entry-screen
The only way I've found to uniquely detect a Tab keystroke from a physical keyboard, is implementing the UIKeyInput protocol's insertText: method on a custom object that canBecomeFirstResponder.
- (void)insertText:(NSString *)text {
NSLog(#"text is equal to tab character: %i", [text isEqualToString:#"\t"]);
}
I didn't get this to work while subclassing UITextField, unfortunately, as UITextField won't allow the insertText: protocol method to get called.
Might help you on the way, though..
I solved this by subclassing UITextField as NextableTextField. That subclass has a property of class UITextField with IBOutlet a hookup.
Build the interface in IB. Set the class of your text field to NextableTextField. Use the connections Inspector to drag a connection to the 'next' field you want to tab to.
In your text field delegate class, add this delegate method...
- (BOOL)textFieldShouldReturn:(UITextField *) textField
{
BOOL didResign = [textField resignFirstResponder];
if (!didResign) return NO;
if ([textField isKindOfClass:[NextableTextField class]])
dispatch_async(dispatch_get_current_queue(), ^{ [[(NextableTextField *)textField nextField] becomeFirstResponder]; });
return YES;
}
BTW - I didn't come up with this; just remember seeing someone else's idea.