22

I have had a look into this question a lot however I'm still unclear whether it's possible or not. Essentially what I want to do is to create a UIPickerView that is continuous in the sense that you can spin it forever and you will never reach the end of it (since the last value is followed by the first value).

I have had a look around online and there seem to be a wide variety of hacks to achieve the desired effect. However a lot of these solutions seem to increase the number of rows in the UIPickerView to trick the user into thinking the UIPickerView is continuous (however, in reality if they kept scrolling they would eventually reach the end).

What I'm after is a way of creating a UIPickerView that is genuinely infinite in the sense that you will never reach the end if you keep scrolling for days, weeks, months or years. I don't mind too much if the solution is a hack since I understand that Apple hasn't provided a way of achieving the effect as of yet.

Please can someone advise on a way of doing this (or point me in the right direction at least)?

10 Answers10

4

I really think, that the only hack you can do with native UIPickerView is described here:

How do you make an UIPickerView component wrap around?

The other way do make really looped picker is to implement it by yourself.

I saw pickers that were implemented with cocos2d, that is OpenGL-based. I think, you can try to do it using UIKit if you really need to.

Or just forget it and make a picker with NSIntegerMax rows with repeatable content. I think that nobody will spin it till the end.

Community
  • 1
  • 1
Morion
  • 10,495
  • 1
  • 24
  • 33
  • Putting NSIntegerMax will generally crash the program. I feel Olie's answer is a better and reasonable one in case of usability and memory. – Rameswar Prasad Jul 15 '17 at 12:02
3

Find the custom UIPickerView class which works very well and is very easy to implement here

Rob
  • 415,655
  • 72
  • 787
  • 1,044
Animesh
  • 1,020
  • 9
  • 8
2

It's possible. Here is how you can do it. First setup a timer. Let's assume int maxNumber is an instance variable set to some arbitrary value.

- ( void) viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
   [self.timer invalidate];
   self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self    selector:@selector(timerFireMethod:) userInfo:nil repeats:YES];

}

- (void) viewWillDisappear:(BOOL)animated{
   [self.timer invalidate];
   [super viewWillDisappear:animated];
}

- (void)viewDidLoad
{
 [super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
 [self.infinitePickerView selectRow:(int)maxNumber*5 inComponent:0 animated:YES];
}

In the timer fire method check if any of the 'edge' view of your uipickerview are showing.

- (void)timerFireMethod:(NSTimer*)theTimer{
int rowSelected = [self.infinitePickerView selectedRowInComponent:0];    
for (int i=0; i<= 20; i++) {
    UIView * viewBelow = [self.infinitePickerView viewForRow:i forComponent:0];
    UIView * viewAbove = [self.infinitePickerView viewForRow:maxNumber*10-20+i forComponent:0];
    if(viewBelow!=nil || viewAbove!=nil){
        int middlePosition = maxNumber * 5 + (rowSelected % maxNumber);
        [self.infinitePickerView selectRow:middlePosition inComponent:0 animated:NO];
        break;
    }
}
}

Note this works because [self.infinitePickerView viewForRow:i forComponent:0]; returns a UIView only if it's visible.

Of course your UIPickerViewDelegate must use be something like

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
   return  maxNumber * 10; //returning a large number
}

 //You cannot use this method
/*
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row  forComponent:(NSInteger)component{}
 You have to use the method below in lieu of it
*/

 - (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view{

 UILabel * label;

 if (!view) {
    label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0,  self.infinitePickerView.bounds.size.width, 30)];
}else {
    label = (UILabel *) view;     
}
[label setText:[NSString stringWithFormat:@" %d", row % maxNumber]];
return label;
}

Hope this works!! :) Good luck!

clearwater82
  • 1,746
  • 14
  • 9
2

There is not a native picker that wraps (not as of iOS7.1 anyway.) You can fake this by something along these lines:

NSArray *picks = @[ @"zero", @"one", @"two", @"three" ]; // 4 entries

// ...

- (NSInteger)pickerView:(UIPickerView *)pickerView 
numberOfRowsInComponent:(NSInteger)component
{
    return (3 * [picks count]); // we're very tricky, displaying only
                                // ...the SECOND set of entries, to
                                // ... give the impression of wrap-around
}

// ...

- (NSString *)pickerView:(UIPickerView *)pickerView 
             titleForRow:(NSInteger)row 
            forComponent:(NSInteger)component
{
    // All "3" sets of entries have the same values;
    // this is what gives the appearance of wrap-around.
    int titleRow = row % [picks count];
    return [picks objectAtIndex: titleRow];
}

// ...

- (void)pickerView:(UIPickerView *)pickerView 
      didSelectRow:(NSInteger)row 
       inComponent:(NSInteger)component
{
    // If the wheel turns to outside our 2nd-set range,
    // ...set it to the matching item in the 2nd set.
    // In this way, we always display what looks like a wrap-around.
    int count = [picks count];
    if ((row <  count)
    ||  (row > (count * 2) )
    {
        [pickerView selectRow: (row % count) inComponent: component animated: NO];
    }
}

You'll have to tweak and adjust to fit your needs, of course, but this is the basic gist of it.

Good luck!

Alex Cio
  • 6,014
  • 5
  • 44
  • 74
Olie
  • 24,597
  • 18
  • 99
  • 131
0

Hm.. I've been looking around, and there are a couple links to a few possibilities, the most outstanding being this one: The Abusive Pickerview, the basic idea being that you populate the pickerview with 3 sets of your data and start and the center set. Whenever the user scrolls to the center of either of the top or bottom sets, you set the row value back to the center of the center set! It seems to be more genuine then making a really long list that will create the illusion of infinity. This solution may be the most effective and simple as to solving the infinite pickerview problem! Hope this helped!

waylonion
  • 6,866
  • 8
  • 51
  • 92
  • Generally, you shouldn't copy and paste your answers from other posts http://stackoverflow.com/questions/10815435/how-to-make-a-circular-uipickerview-iphone-ipad – pasawaya Jun 07 '12 at 17:12
0

It seems to me that solutions requiring large amounts of data be stuffed into the pickerView are wasteful. The following solution uses smarts instead of memory.

The only rows that need to be populated are the rows visible in the view and the row immediately preceding the first one and the row immediately following the last one. When the user scrolls a component, pickerView:titleForRow:forComponent: is called for each row that scrolls by.

So the solution is to update the data in pickerView:titleForRow:forComponent: and then call the appropriate method to reload the data so that it is there when the pickerview scrolls one more tick.

Victor Engel
  • 2,037
  • 2
  • 25
  • 46
0

I know it's an old question, but I've just found it as I'm implementing it in my own project.

Just use Morion's method of a large number of items with repeating content, then every time it stops scrolling, reset the row. The only way anyone will ever figure out you've cheated is if they scroll continuously without a break until it stops, never calling the didSelectRow function. It would take some dedication!

Regardless of how you do it, it seems to me that it's going to feel a bit hacky, & this must be the easiest hack...

SomaMan
  • 4,127
  • 1
  • 34
  • 45
0

I have made a cyclic tableView based on UIScrollView. And based on this tableView, I re-implement UIPickerView. You might be interested in this. And this picker view has all the feature that UIPickerView, but also gives many new features, and custom this picker view is much easier.

https://github.com/danleechina/DLPickerView

And be aware of that this DLPickerView cyclically scroll is really scrolling cyclically. All the magic happened because of another class DLTableView.

Dan Lee
  • 101
  • 1
  • 5
0

I used pickerView Selection to create a circular one:

   import UIKit
   class ViewController:        
 UIViewController,UIPickerViewDelegate,UIPickerViewDataSource
{
@IBOutlet weak var picker: UIPickerView!
@IBOutlet weak var myLabel: UILabel!

let numbers = [0,1,2,3,4,5,6,7,8,9]

override func viewDidLoad()
{
    super.viewDidLoad()
    // A.Select Second Row
    picker.selectRow(1, inComponent: 0, animated: false)
}
func numberOfComponents(in pickerView: UIPickerView) -> Int
{
    return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int
{
     //B. Add 2 rows to your array count  
    return numbers.count + 2
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String?
{
    var rowTitle = ""
    switch row
    {
    case 0:
        // C. Set first row title to last component of the array.
         // This row is not visible to the user as it is hidden by the        

         //selection in ViewDidLoad
        rowTitle = "\(numbers.last!)"
    case numbers.count + 1:
        //D. Set last row title to first array component
        rowTitle = "\(numbers.first!)"
    default:
        // E. Remember [row - 1] to avoid errors
        rowTitle = "\(numbers[row - 1])"
    }
    return rowTitle
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int)
{
    var theText = ""
    switch row
    {
    case 0:
      //F. Select Row at array count to load the last row
        pickerView.selectRow(numbers.count , inComponent: 0, animated: false)
        theText = = "\(numbers.last!)"
    case numbers.count + 1:
     //G. This Selection will set the picker to initial state
        pickerView.selectRow(1, inComponent: 0, animated: false)
        theText = "\(numbers.first!)"
    default:
        theText = "\(numbers[row - 1])"
    }
    myLabel.text = theText

}

  }
Atka
  • 547
  • 4
  • 7
0

A very simple solution that will make u think there is no end of the picker view, by scale up the array and make modulus pick the corresponded item from the array

override func viewDidLoad(){
    super.viewDidLoad()
    self.yourArray.selectRow((yourArray.count*100)/2, inComponent: 0, animated: false)
}

func pickerView(_ pickerView: UIPickerView, numberOfRowaInComponent component: Int) -> Int{
    return yourArray.count*100
}

func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int)->String{
    return yourArray[row % yourArray.count]
}
Torped
  • 1