Swift5 UIPickerview: getting scroll flags [duplicate] - ios

I have a customized UIPickerview and I do not want to use a datepicker. I want to implement the feature where when a user scrolls down/up the hours, the AM/PM component switches while the hour is scrolling. This means that I need to switch it before pickerView didSelectRow is called. Is there a way to do this?
Thanks

Use following method,
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
// put your logic here.
}
Above method is from UIPickerViewDelegate, If user selects any element using pickerview, this method is automatically triggered.
Hope it helps to you.
Edit :
I think you should use following method for detecting - in which direction user is scrolling ?
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
NSLog(#"scrolling to row is %#",[NSString stringWithFormat:#"index_%i",row]);
return [NSString stringWithFormat:#"index_%i",row];
}
whenever user is scrolling up/down above method is triggered automatically which is similar to UITableView. Remember UIPickerView is using UIPickerTableView which is private, so we can not detect the scrolling the way you want to have.
Let me explain the detecting the direction in pickerview.
Example. Visible rows are index_4,index_5,index_6,index_7. Now if user is scrolling down index_8 will be called. Similarly if user is scrolling to up index_3 will be called.
I hope this trick will solve your problem. Even-though let me know your feedback on this.

There is a trick to detect this but there is no delegate method/ property to detect if its scrolling or not
take a property as isScrolling
set isScrolling to true in func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? or equivalent method
set isScrolling to false in func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int)
hope this helps, BTW this is what is explained in all the above mentioned answers

have to use this two UIPickerView delegate method for event:
DID END SCROLL:
- (void)pickerView:(UIPickerView *)pV didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
NSLog(#"pickerView didSelectRow %zd",row);
// DID END SCROLL: CALL YOUR HEANDLER METHOD!!
}
WILL START SCROLLING:
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view{
// WILL START SCROLLING: CALL YOUR HEANDLER METHOD!!
UILabel* label = (UILabel*)view;
if (!label){
label = [UILabel new];
[label setFrame:frame];
[label setBackgroundColor:[UIColor clearColor]];
[label setFont:[UIFont fontWithName:FONT_QUICKSAND_BOLD size:12]];
[label setTextAlignment:NSTextAlignmentRight];
[label setLineBreakMode:NSLineBreakByTruncatingMiddle];
[label setTextColor:UIColorFromRGB(0X2692ff)];
}
//Update labeltext here
[label setText:[NSString stringWithFormat:#"row %zd",row]];
return label;
}
obviosly except for the building of UIPickerview (firstTIME!); so u have to implement a fake didendscroll event when you finish to build you piker or ignore first willstart scroll for the visible piker row

Unfortunately, the above solutions (setting a flag when data source methods such as titleForRow are called and resetting it when didSelectRow is called) are sketchy and won't work in many cases. The data source methods may be called in many cases where there is no scrolling by the user - such as UI changes resulting in changing view layouts, and also may be called immediately after didSelectRow - you have no control over when these methods are called.
In relation to this particular question, these solutions may work, since these methods are always called when scrolling - just not ONLY when scrolling. However, care needs to be taken not to assume that the user MUST be scrolling in these cases. Also, if you manage a state machine with this flag (like disabling some button when scrolling and enabling it after), you'll get in trouble.
Finally, this only allows detecting a scroll (with the above caveats), but it won't tell you the speed or current values - so it would be difficult to tell when to switch the AM/PM label.
The only way I was able to reliably detect a scroll was through a mess of flags that I set when UI changes are made and other ugly hacks (like waiting 100 ms after a didSelectRow before trying to detect scrolls again, because I noticed calls to the data source immediately after didSelectRow). Adding willScroll/didScroll methods to the delegate seems like an obvious thing Apple neglected to add.

I just found a way to achieve "pickerDidScroll",it work fine. The key point is add KVO to the row view. Here is my code:
//picker view delegate
- (UIView*)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view {
if (!view) {
view = [[RatchetScrollUnit alloc] initWithFrame:CGRectZero];
//
[((RatchetScrollUnit*)view) addRatchetObserver:self];
}
return view;
}
//KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (self.ratchet) {
NSInteger row = [self.ratchet selectedRowInComponent:0];
if (row != _lastRow) {
_lastRow = row;
if (self.delegate && [self.delegate respondsToSelector:#selector(ratchetScrollerDidRatchetedToTooth:)]) {
[self.delegate ratchetScrollerDidRatchetedToTooth:row];
}
}
}
}
The "RatchetScrollUnit" class :
#interface RatchetScrollUnit ()
#property (nonatomic,assign) BOOL observed;
#property (nonatomic,weak) NSObject *myObserver;
#end
#implementation RatchetScrollUnit
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_observed = NO;
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (void)dealloc {
if (self.observed) {
[self removeObserver:self.myObserver forKeyPath:#"frame" context:nil];
}
}
- (void)addRatchetObserver:(NSObject*)observer {
if (self.observed) {
return;
}
self.observed = YES;
self.myObserver = observer;
//
[self addObserver:observer
forKeyPath:#"frame"
options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew
context:nil];
}
Have a try

Related

iPhone - Editing UITextField Moving Other Views to the Right

In my initial view controller that is loaded, one of my subviews is a UITextField. I have other subviews of the main view as well (i.e. four labels, one pickerview, and two buttons). When I try to edit the textfield, all of the other subviews shift to the right. I have no idea why this is happening. I have no code even applying to what happens while editing the textfield. But, I do have some code that serves to align all of my subviews (including the textfield). Here is that code:
- (void)viewDidLoad {
[super viewDidLoad];
self.stopPickerView.dataSource = self;
self.stopPickerView.delegate = self;
selectedEntry=#"";
self.inputField.delegate=self;
// Do any additional setup after loading the view, typically from a nib.
}
-(void)viewDidAppear:(BOOL)animated{
int width=self.view.bounds.size.width;
int height=self.view.bounds.size.height;
headingLabel.frame=CGRectMake(headingLabel.frame.origin.x, headingLabel.frame.origin.y,self.view.bounds.size.width-self.view.bounds.size.width/5, headingLabel.frame.size.height);
headingLabel.center=CGPointMake(width/2,height/9);
stopPickerView.center=CGPointMake(width/2, height/5);
setStop.center=CGPointMake(width/2, height/2.2);
orLabel.center=CGPointMake(width/2, height/1.7);
wakeUpLabelOne.frame=CGRectMake(wakeUpLabelOne.frame.origin.x, wakeUpLabelOne.frame.origin.y,self.view.bounds.size.width-self.view.bounds.size.width/2, self.view.bounds.size.height-self.view.bounds.size.height/19.2);
wakeUpLabelOne.center=CGPointMake(width/3.5, height/1.4);
NSLog(#"%f",wakeUpLabelOne.frame.size.width);
wakeUpLabelTwo.center=CGPointMake(width/1.1, height/1.4);
inputField.center=CGPointMake(width/1.5, height/1.4);
finalSetButton.center=CGPointMake(width/2, height/1.2);
[wakeUpLabelTwo adjustsFontSizeToFitWidth];
}
- (int)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 1;
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
selectedEntry = [_pickerData objectAtIndex:row];
}
// The number of rows of data
- (int)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
return _pickerData.count;
}
// The data to return for the row and component (column) that's being passed in
- (NSString*)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
return _pickerData[row];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[inputField resignFirstResponder];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return NO;
}
If you have any ideas to why this is happening or you would like more information, just leave it in the comments.
I figured out why this was happening. For some reason, putting my frame configurations in the viewDidAppear method was a bad idea and was messing something up. The fix to this was to just put my frame configurations in the viewDidLayoutSubviews method instead. The problem was immediately fixed.

UIPickerView pickerView:titleForRow:rowforComponent method not called

I have a UIPickerView in my storyboard.
The UIViewController containing it has interface declarations set
#interface ContactDetail : UIViewController <ABPeoplePickerNavigationControllerDelegate, UIPickerViewDataSource, UIPickerViewDelegate,UIAlertViewDelegate, UITextFieldDelegate>
on view Load, pickerview delegation set
pickerView_RelationshipType.dataSource=self;
pickerView_RelationshipType.delegate = self;
pickerView_RelationshipType.showsSelectionIndicator = YES;
all UIPickerView datasource and delegate methods are triggered except those 2
pickerView:titleForRow:rowforComponent
and
pickerView:viewForRow:forComponent:reusingView
those following are called, no problem
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
- (void)pickerView:(UIPickerView *)pickerView didSelectRow: (NSInteger)row inComponent:(NSInteger)component
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component
what's in numberOfComponentsInPickerView ?
If your numberOfRows returning 0, then your delegate method will never be called - the picker won't ask for the title of a row if it doesn't think it has any rows to display.
Make sure your data source array is allocated and initialized. I ran into this problem when everything was hooked up correctly (I set the data source and delegates and was conforming to the protocols), but I forgot to initialize my data source arrays before adding objects to them. My code was hitting numberOfComponentsInPickerView and numberOfRowsInComponent but not titleForRow and I couldn't figure out why. Make sure your data source is not returning nil.
Kind of a stupid edge case, but I figured I'd add my answer in case someone else runs into the same issue.

How do I make a picker view populate from an array of NSStrings? How would I then access the value of the item picked?

Okay, I need a picker so that a user can select from a list of predefined options. Can someone give me the easy, simplified version of how to populate a picker view from an array of NSStrings? Then, how do I read that value from the picker? I'm noticing that things like nameOfPicker.value and nameOfPicker.text do not work here.
Thank you!
I have already read the following documents and I don't really understand what they are getting at.
https://developer.apple.com/library/ios/documentation/userexperience/conceptual/UIKitUICatalog/UIPickerView.html
https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaEncyclopedia/DelegatesandDataSources/DelegatesandDataSources.html#//apple_ref/doc/uid/TP40010810-CH11
It is quite similar to how you populate data in a UITableView, by setting datasource and delegate.
1. First step is to set the delegate of the picker view. In .m file you set your datasource and delegate
#interface YourViewController () <UIPickerViewDataSource, UIPickerViewDelegate>
{
}
#property (strong, nonatomic) UIPickerView *yourPickerView;
2. Assign the datasource and delegate
self.yourPickerView.delegate = self;
self.yourPickerView.datasource = self;
3. Implement datasource and delegate
// returns the number of 'columns' to display.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 1;
}
// returns the # of rows in each component..
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
return self.yourArrayofStrings.count;
}
//The title that should be shown in picker view
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
NSString *yourTitle = [self.yourArrayofStrings objectAtIndex:row]
return yourTitle;
}
//This is called when Picker view value is selected
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
NSLog #(#"Selected row = %#",[self.yourArrayofStrings objectAtIndex:row]);
}

UIPickerView select first row programmatically

There are a lot of questions like this, but I believe my case is unique.
I have a pickerview that pops up when a textbox is clicked inside. The user selects their location in the pickerview, and it's put into the textbox.
When opening the pickerview, the slider is on the first element, but if i click the done button to minimize the pickerview, that no element is selected (i.e. you must scroll down and back up to select the first element)
I've tried
[pickerView selectRow:0 inComponent:0 animated:YES];
and various combinations of it, but it only puts the slider on that element, again not actually selecting it.
Some answers said to apply the above code in the viewDidLoad() method, but unfortunately I'm pulling the location array in from a web service and cannot do that.
Ideally, I'd like to work as - the user clicks in the textbox, the pickerview pops up as normal, if at that point the done button is clicked, the first item is selected.
Thankyou in advance
Swift 3+
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == yourTextField{
self.yourPickerView.selectRow(0, inComponent: 0, animated: true)
self.pickerView(yourPickerView, didSelectRow: 0, inComponent: 0)
}
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
//Do all the stuff to populate the TextField
}
Here is something you can try :
Set a var in your #interface, something like NSString *selectedString;.
When you init your UIPickerView (in pickerView:titleForRow:forComponent:), when you set the title for the row 0, set selectedString with the same string as the title of the row #0. Then, in pickerView:didSelectRow:inComponent:, set selectedString with the appropiate string :
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
// Some stuff
// Assuming "yourDataArray" contains your strings
selectedString = [yourDataArray objectAtIndex:row];
}
Then, when you call the method triggered by the push of the "done" button, use selectedString to set the text.
Hope this works out for you !
I had a same problem. My solution was this:
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
// Only for the text field with the picker
if (textField == self.yourTextField)
{
// Select the first row programmatically
[self.categoriesPicker selectRow:0 inComponent:0 animated:YES];
// The delegate method isn't called if the row is selected programmatically
[self pickerView:self.categoriesPicker didSelectRow:0 inComponent:0];
}
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
//Do all the stuff to populate the TextField
}
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
// Only for the text field with the picker
if (textField == self.yourTextField && [textField length]!=0)
{
// Select the first row programmatically
[self.categoriesPicker selectRow:0 inComponent:0 animated:YES];
// The delegate method isn't called if the row is selected programmatically
[self pickerView:self.categoriesPicker didSelectRow:0 inComponent:0];
}
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
//Do all the stuff to populate the TextField
}
/Create a IBACTION for the textfield with event editing did begin and the function should check if field is empty like this/
-(IBAction)asignFirst:(UITextField *)sender{
if ([sender.text isEqualToString:#""])
{
[yourPicker selectRow:0 inComponent:0 animated:YES];
sender.text = self.pickerData[0];
}
}

How do I reload/refresh the UIPickerView (with new data array) based on button press?

IF I wanted a picker, for states/provinces, i haven't seen an example, but I mocked up shown above, a picker for US/Can/Mex. wondering can you dynamically switch the NSMutableArray for the UIPickerView and then have it somehow reload each time you click on US/Can/Mex buttons??? How do I go about doing this. What approach do I take. Looking for someone to point a beginner for a clue in the right direction.
You will have to implement the datasource and the delegate.
once you press a button, set an array pointer to the appropriate array.
than call [thePicker reloadAllComponents];
-(IBAction) usButtonPressed:(id)sender{
self.inputArray = self.usArray;
[self.thePicker reloadAllComponents];
}
-(IBAction) mexicoButtonPressed:(id)sender{
self.inputArray = self.mexicoArray;
[self.thePicker reloadAllComponents];
}
the datasource methods:
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
return [self.inputArray count];
}
the delegate methods:
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
return [self.inputArray objectAtIndex:row];
}
Swift: xcode 6.1
// reload the component of picker (Used this inside the trigger action or button)
[self.pickerControl reloadAllComponents];
In swift 3,4,or 5 you just write in didSelectRow function like bellow
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.view.endEditing(false)
yourPickerView.selectRow(0, inComponent: 0, animated: true)
yourPickerView.reloadAllComponents();
}
By calling
thePicker.delegate = self;
again, I was able to force refresh the pickerview with new info
Use multiple pickers, or use a switch block to determine which data should be loaded, and reload the picker.
In your button press action,
[self.pickerView selectRow:0 inComponent:0 animated:YES];

Resources