Has anyone gotten a UIPickerView setup and working with the Objective-C bridge?

I’d like to use Codea’s Objective-C bridge to make a UIPickerView control so I can display choosable lists in future projects. I’ve been trying to setup myself but haven’t been able to figure out how to do it yet following Apple’s tutorials + Codea’s reference.

Has anyone else figured out how to create a UIPickerView control?

Thanks :blush:

Hi @SugarRay!

Here’s an example. I used this tutorial: How to create Picker programmatically from array in iOS?, and the documentation under UIPickerView | Apple Developer Documentation.

I hope this helps :slight_smile:

-- Picker

Delegate = objc.delegate("UIPickerViewDelegate")

DataSource = objc.delegate("UIPickerViewDataSource")

data = { "A", "B", "C" }

function DataSource:numberOfComponentsInPickerView_(objPickerView)
    return 1
end

function DataSource:pickerView_numberOfRowsInComponent_(objPickerView, intComponent)
    return #data
end

function Delegate:pickerView_titleForRow_forComponent_(objPickerView, intRow, intComponent)
    return data[intRow + 1]
end

function Delegate:pickerView_didSelectRow_inComponent_(objPickerView, intRow, intComponent)
    selectedIndex = intRow + 1
end

--- picker: objc.UIPickerView
function setup()
    viewer.mode = FULLSCREEN
    
    picker = objc.UIPickerView()
    picker.delegate = Delegate()
    picker.dataSource = DataSource()
    
    --- view: objc.UIView
    view = objc.viewer.view
    view:addSubview_(picker)
    picker.center = view.center
    
    selectIndex(2)
end

function willClose()
    picker:removeFromSuperview()
end

function selectIndex(index)
    selectedIndex = index
    picker:selectRow_inColumn_animated_(index - 1, 0, true)
end

function touched(touch)
    if touch.state == BEGAN and touch.y < 200 then
        local index = (selectedIndex % #data) + 1
        selectIndex(index)
    end
end

function draw()
    background(0, 174, 255)
    strokeWidth(5)
    
    fontSize(64)
    fill(0)
    textMode(CENTER)
    text(data[selectedIndex], WIDTH / 2, 200)
    
    fontSize(32)
    text("Tap here to cycle", WIDTH / 2, 150)
end

Wow, thanks so much @jfperusse-- that works perfectly!

Only question I had on your example is when does function willClose() get called?

I read a lot of the tutorials (including the ones you cited) and couldn’t figure out how to convert that information to the syntax of the Codea Objective-C bridge. Actually seems much simpler than the actual steps in those tutorials; for instance, I was getting hung up on trying to figure out how to use Objective-C “protocols”: I thought (as one of those tutorials mentioned) that I had to tell the Codea’s ViewController to use the UIPickerViewDelegate and UIPickerViewDataSource protocols, but I see from your example that I don’t have to tell Codea’s ViewController to use any protocols at all (I was trying to figure out how to use the “Protocol” keyword that pops up in Codea’s auto-complete). Instead, it looks like all that needs to be done in Codea’s Objective-C bridge is:

  1. Define two standard Objective-C delegates (UIPickerViewDelgate and UIPickerViewDataSource)
  2. Define and instantiate a UIPickerView
  3. Set UIPickerView’s .delegate and .dataSource to = the delegates defined in #1
  4. Set the UIPickerView to be a subset of UIView to render it
    *And remembering that Objective-C array (e.g. “row” in the UIPickerView) indices start at zero vs. lua’s table indices start at one (and hence your +/- 1 conversion between the two types of arrays in your example code above)

One challenge of I’ve had in converting the Apple and other Objective-C examples to the Codea Objective-C bridge is with some of the subtle syntax changes that don’t seem to be specifically mentioned in the Reference; e.g:

objPickerView instead of objUIPickerView, intRow instead of introw (which it would seem the syntax should be from Apple’s Objective C documentation)

Thanks again for that very useful example-- I’m off to now writing some programs that pull data from public databases with webR and will use the UIPickerView to help the user choose selections.

Happy Holidays :blush:

Hi @SugarRay! Indeed, our naming might be confusing, but objc.delegate is what can be used to implement Objective-C protocols by simply implementing the required methods on the created delegate type. You sumed up what needs to be done pretty well.

The naming of the parameters is not really important. As per Codea’s documentation, the important part is the first character of each parameter (o for object, i for integer, etc.).

However, the naming of the actual methods is extremely important.

So when you see something like this in Apple’s Objective-C documentation:

- (void)selectRow:(NSInteger)row 
      inComponent:(NSInteger)component 
         animated:(BOOL)animated;

The important parts are before each ‘:’. The parameter names are not important, only their type is.

You concatenate the method parts with _, and must always end with _.

Thus the above becomes selectRow_inComponent_animated_(intRow, intComponent, bAnimated), but the parameter names could be integerRow, iComponent, booleanAnimated, etc. and it would work as well.

Happy holidays!

Thanks for the clarification, @jfperusse, that will help all lot figuring out how to use other Objective-C classes and methods with Codea.

From your initial example, I’m rapidly advancing in my understanding of how to use UIPicker and figured out how to resize it, change fonts inside of it, and use multiple components. I’ll aim to post a supplemental example on this thread soon using multiple components in case others are looking for additional examples. :blush:

1 Like

So, here’s the promised additional bells and whistles example for those interested in exploring the use of Objective-C UIPicker View. Thanks again to @jfperusse, for helping unlock the syntax for using UIPicker View in Codea. The following example adds to his example by illustrating a two-component picker in addition to examples on how to change the formatting and text of the UIPicker view. It includes the use of UILabels and UIButtons. Apple’s auto layout syntax seems quite tedious (Codea’s text and graphical rendering seems much simpler) but does produce a nice GUI layout end-result:

-- UIButtonsLabels&PickerView

function setup()
    viewer.mode = FULLSCREEN
    
    --note: the next statement gets the actual UIViewController running in Codea
    local vc = objc.viewer
    
    -- setup UIPicker's delegates and their methods ("Delegate" and "DataSource")
    -- Picker (note: row and components (Objective C arrays) start at index 0 not 1 like lua so need +/- offset lua tbl index)
    Delegate = objc.delegate("UIPickerViewDelegate")
    DataSource = objc.delegate("UIPickerViewDataSource")
    -- structure table as {{component1},...}
    data = {{" Mark "," Lori "," Bob "," Shane "},{" lua ", " javascript ", " R ", " Objective-C "}}
    -- for Objective-C delegates, need to ensure that parameter names preceded by type (e.g. obj)
    function DataSource:numberOfComponentsInPickerView_(objPickerView)
        return #data -- == # of components or 1st order of the lua table (array)
    end
    function DataSource:pickerView_numberOfRowsInComponent_(objPickerView, intComponent)
        return #data[intComponent+1] -- ok for array type tables but won't work for dictionary tables
    end
    function Delegate:pickerView_viewForRow_forComponent_reusingView_(objPickerView, intRow, intComponent, objView)
        -- note can also test for component case and set text characteristics differently per component
        local label = objc.UILabel()
        label.font = objc.UIFont:systemFontOfSize_weight_(40,500)
        label.textColor = color(0,0,0)
        label.text = data[intComponent+1][intRow+1]
        return label
    end
    
    -- note: if you don't need to resize picker text size, use the following simpler Delegate method instead of above method:
    -- function Delegate:pickerView_titleForRow_forComponent_(objPickerView, intRow, intComponent)
    --      return data[intComponent+1][intRow + 1] -- compensates for lua index being Objc Index + 1
    -- end
    
    --  now setup UIPickerView object    
    picker = objc.UIPickerView()
    picker.delegate = Delegate()
    picker.dataSource = DataSource()
    picker.backgroundColor = color(255,255,255)
    picker.textColor = color(0,0,0)
    picker.hidden = true
    vc.view:addSubview_(picker)
    picker.translatesAutoresizingMaskIntoConstraints = false
    picker.centerXAnchor:constraintEqualToAnchor_(vc.view.centerXAnchor).active = true
    picker.topAnchor:constraintEqualToAnchor_constant_(vc.view.topAnchor, HEIGHT/2 -40).active = true
    picker.heightAnchor:constraintEqualToConstant_(300).active = true
    picker.widthAnchor:constraintEqualToConstant_(600).active = true
    
    -- setup labels
    local nameLabel = objc.UILabel()
    local langLabel = objc.UILabel()
    local nameLabelResult = objc.UILabel()
    local langLabelResult = objc.UILabel()
    nameLabel.text = "Name: "
    langLabel.text = "Preferred Language: "
    nameLabelResult.text = ""
    langLabelResult.text = ""
    nameLabel.textColor = color(255,255,255)
    langLabel.textColor = color(255,255,255)
    nameLabelResult.textColor = color(0,0,0)
    langLabelResult.textColor = color(0,0,0)
    nameLabelResult.backgroundColor = color(255,255,255)
    langLabelResult.backgroundColor = color(255,255,255)
    nameLabel.font = objc.UIFont:systemFontOfSize_(40)
    langLabel.font = objc.UIFont:systemFontOfSize_(40)
    nameLabelResult.font = objc.UIFont:systemFontOfSize_(40)
    langLabelResult.font = objc.UIFont:systemFontOfSize_(40)
    vc.view:addSubview_(nameLabel)
    vc.view:addSubview_(langLabel)
    vc.view:addSubview_(nameLabelResult)
    vc.view:addSubview_(langLabelResult)
    nameLabel.translatesAutoresizingMaskIntoConstraints = false
    langLabel.translatesAutoresizingMaskIntoConstraints = false
    nameLabelResult.translatesAutoresizingMaskIntoConstraints = false
    langLabelResult.translatesAutoresizingMaskIntoConstraints = false
    nameLabel.topAnchor:constraintEqualToAnchor_constant_(vc.view.topAnchor, 40).active = true
    nameLabel.leadingAnchor:constraintEqualToAnchor_constant_(vc.view.leadingAnchor, 80).active = true
    langLabel.topAnchor:constraintEqualToAnchor_constant_(nameLabel.bottomAnchor, 20).active = true
    langLabel.leadingAnchor:constraintEqualToAnchor_constant_(vc.view.leadingAnchor, 80).active = true
    nameLabelResult.topAnchor:constraintEqualToAnchor_constant_(vc.view.topAnchor, 40).active = true
    nameLabelResult.trailingAnchor:constraintEqualToAnchor_constant_(vc.view.trailingAnchor, -80).active = true
    langLabelResult.topAnchor:constraintEqualToAnchor_constant_(nameLabelResult.bottomAnchor, 20).active = true
    langLabelResult.trailingAnchor:constraintEqualToAnchor_constant_(vc.view.trailingAnchor, -80).active = true
    
    -- setup buttons
    local uiButton = objc.UIButton
    local changeAction = objc.UIAction:actionWithHandler_(function(objAction) picker.hidden, saveButton.hidden, cancelButton.hidden = false,false,false end)
    local saveAction = objc.UIAction:actionWithHandler_(function(objAction) nameLabelResult.text = data[1][(picker:selectedRowInComponent_(0))+1]; langLabelResult.text = data[2][(picker:selectedRowInComponent_(1))+1]; end)
    local cancelAction = objc.UIAction:actionWithHandler_(function(objAction) picker.hidden, saveButton.hidden, cancelButton.hidden = true,true,true end)
    changeButton= uiButton:buttonWithType_primaryAction_(objc.enum.UIButtonType.system, changeAction)
    saveButton= uiButton:buttonWithType_primaryAction_(objc.enum.UIButtonType.system, saveAction)
    cancelButton= uiButton:buttonWithType_primaryAction_(objc.enum.UIButtonType.system, cancelAction)
    changeButton:setTitle_forState_(" change ", objc.enum.UIControlState.normal)
    saveButton:setTitle_forState_(" save ", objc.enum.UIControlState.normal)
    cancelButton:setTitle_forState_(" cancel ", objc.enum.UIControlState.normal)
    changeButton:setTitleColor_forState_(color(255,165,0), objc.enum.UIControlState.normal)
    saveButton:setTitleColor_forState_(color(0,255,0))
    cancelButton:setTitleColor_forState_(color(255,0,0))
    changeButton:setBackgroundColor_(color(220,220,220))
    saveButton:setBackgroundColor_(color(220,220,220))
    cancelButton:setBackgroundColor_(color(220,220,220))
    saveButton.hidden = true
    cancelButton.hidden = true
    vc.view:addSubview_(changeButton)
    vc.view:addSubview_(saveButton)
    vc.view:addSubview_(cancelButton)
    -- layout using Apple's auto-layout functionality 
    changeButton.translatesAutoresizingMaskIntoConstraints = false
    saveButton.translatesAutoresizingMaskIntoConstraints = false
    cancelButton.translatesAutoresizingMaskIntoConstraints = false
    changeButton.centerXAnchor:constraintEqualToAnchor_(vc.view.centerXAnchor).active = true
    changeButton.bottomAnchor:constraintEqualToAnchor_(picker.topAnchor).active = true
    saveButton.bottomAnchor:constraintEqualToAnchor_(picker.topAnchor).active = true
    saveButton.leadingAnchor:constraintEqualToAnchor_(picker.leadingAnchor).active = true
    cancelButton.bottomAnchor:constraintEqualToAnchor_(picker.topAnchor).active = true
    cancelButton.trailingAnchor:constraintEqualToAnchor_(picker.trailingAnchor).active = true  
end