I own a vpn app. Our app comes predefined with one server, this server is hard coded into the app. It looks like this; let serverAddress = "0.0.0.0" (but with a real server ip).
I have made a picker view using the following:
let IPs = ["GER1", "UK1", "GER2", "UK2"]
I now want to know how to say " GER1=192.168.1.2 UK1=192.168.1.3 GER2=192.168.1.4 UK2=192.168.1.5 "
and then parse it to let serverAddress = "0.0.0.0" replacing 0.0.0.0 with the selected ip.
In summary:
I only have 1 server specified, If I ever wanted to change it I'd change the let serverAddress = "0.0.0.0" but I would like to add more servers as an option. so I somehow need to change 0.0.0.0
I personally think picker view is the best way of doing it. But I am open to ideas.
The source code I am working with is here: https://github.com/lxdcn/NEPacketTunnelVPNDemo
First off, you should change the serverAddress to a var instead of a let.
Then, you should use the delegate function: didSelectRow, to change serverAddress to which server address that was selected.
To achieve this, I recommend using a dictionary to store all of your servers to select from like so:
class YourViewController {
// MARK: - Server Address
var serverAddress = "0.0.0.0"
// MARK: Picker View Resources
let serverPicker = UIPickerView() {
didSet {
serverPicker.delegate = self
serverPicker.dataSource = self
}
}
var serverPickerData:[(key: String, value: String)] = [("base", "0.0.0.0"), ("ger1", "192.68.1.2"), ("uk1", "192.68.1.3"), ("ger2", "192.68.1.4"), ("uk2", "192.68.1.5")]
....
}
extension YourViewController: UIPickerViewDelegate, UIPickerViewDataSource {
// MARK: - UIPicker View Delegate
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return serverPickerData.count
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
serverAddress = serverPickerData[row].value
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return "\(serverPickerData[row].key) - \(serverPickerData[row].value)"
}
}
Then whenever you need to add a server to the UIPicker, you can simply push a new dictionary value onto your dictionary
Related
I have encountered some synchronisation/graphic update problems with my UIPickerView.
I want a view with 2 components, where the content of the second component depends on the selected row of the first component.
My code is inspired from: Swift UIPickerView 1st component changes 2nd components data
However, while it seems to work, sometimes (not every time) there are some visual problems, as seen on the screenshots below. (on the second screenshot, you can see that the rows of the second component are not really correct, and are a mix of the rows from the first and the second component)
Here is the code:
import UIKit
class AddActivityViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var typePicker: UIPickerView!
var pickerData: [(String,[String])] = []
override func viewDidLoad() {
super.viewDidLoad()
self.typePicker.delegate = self
self.typePicker.dataSource = self
pickerData = [("sport",["bike", "run", "soccer", "basketball"]),
("games",["videogame", "boardgame", "adventuregame"])]
// not sure if necessary
typePicker.reloadAllComponents()
typePicker.selectRow(0, inComponent: 0, animated: false)
// pickerData = [("sport",["bike", "run", "soccer"]),
// ("games",["videogame", "boardgame", "adventuregame"])]
}
// number of columns in Picker
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
// number of rows per column in Picker
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
print("function 1 called")
if component == 0 {
return pickerData.count
} else {
let selectedRowInFirstComponent = pickerView.selectedRow(inComponent: 0)
return pickerData[selectedRowInFirstComponent].1.count
}
}
// what to show for a specific row (row) and column (component)
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
print("function 2 called with values: component: \(component), row: \(row)")
if component == 0 {
// refresh and reset 2nd component everytime another 1st component is chosen
pickerView.reloadComponent(1)
pickerView.selectRow(0, inComponent: 1, animated: true)
// return the first value of the tuple (so the category name) at index row
return pickerData[row].0
} else {
// component is 1, so we look which row is selected in the first component
let selectedRowInFirstComponent = pickerView.selectedRow(inComponent: 0)
// we check if the selected row is the minimum of the given row index and the amount of elements in a given category tuple array
print("---",row, (pickerData[selectedRowInFirstComponent].1.count)-1)
let safeRowIndex = min(row, (pickerData[selectedRowInFirstComponent].1.count)-1)
return pickerData[selectedRowInFirstComponent].1[safeRowIndex]
}
//return pickerData[component].1[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
// This method is triggered whenever the user makes a change to the picker selection.
// The parameter named row and component represents what was selected.
}
}
Is this a problem with my code or generally a complicated aspect of UIPickers that can not be trivially solved?
Additionally, is there a nicer way to develop this functionality?
I solved the error, however I do not understand why this solves it.
The solution is to imlement the func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int)method, which I did not believe to be necessary just to show the fields.
In other words, just add this to my existing code:
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if component == 0 {
pickerView.reloadComponent(1)
} else {
let selectedRowInFirstComponent = pickerView.selectedRow(inComponent: 0)
print(pickerData[selectedRowInFirstComponent].1[row])
}
}
I don't know what I've done to anger the gods but my UIPickerView is behaving abnormally. It has 2 components, for month and year values, and selecting a value on the right component (year) causes the left component (month) to change value as well.
I initially create the UIPickerView using the following code:
pickerView = UIPickerView(frame: ...)
pickerView.delegate = self
pickerView.dataSource = self
Then I implement some simple delegate / data source methods to handle the picker view, as follows:
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if component == 0 {
expirationMonth = months[row]
} else {
expirationYear = years[row]
}
expirationTextField?.text = "\(expirationMonth ?? "")/\(expirationYear?.substring(from: 2) ?? "")"
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if component == 0 {
return months[row]
} else {
return years[row]
}
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
return months.count
} else {
return years.count
}
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
Additionally, the UIPickerView data source method numberOfComponents is being called even when I don't set the picker view's data source.
I have no idea what's causing this behavior. I've tried pretty much everything and nothing is stopping it from changing both column's values. Does anyone have any ideas to fix this?
Turns out it's a bug in the iOS simulator, because when running it on my iPhone it worked like a charm. I thought I was going crazy there for a second!
I'm going to file a bug complaint in the morning...Good luck to anyone else with this issue.
I'm working with XCode 8.2.1, Swift 3 and iOS10.
I've a list of items with the following format:
ID | Name
---------
1 | John
2 | Maria
3 | Peter
4 | Roger
The code looks like this:
var formsList = [1:"John", 2:"Maria", 3:"Peter", 4:"Roger"]
What I want to do is to set that data into an UIPickerView, so when someone chooses for example John, the ID 1 is returned, or if someone chooses Peter, the ID 3 is returned.
I do other stuff once I get that ID, that's why I need it.
Any idea or suggestion on how I can achieve this?
Thanks!
You just need to sort your dictionary by its keys and use it as your picker data source:
let formsList = [1:"John", 2:"Maria", 3:"Peter", 4:"Roger"]
let dataSource = formsList.sorted{$0.key<$1.key}
This way you have all your dictionary names sorted in an array including their IDs. Your picker should look something like this:
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var pickerView: UIPickerView!
#IBOutlet weak var label: UILabel!
let formsList = [1:"John", 2:"Maria", 3:"Peter", 4:"Roger"]
var dataSource: [(key: Int, value: String)] = []
override func viewDidLoad() {
super.viewDidLoad()
dataSource = formsList.sorted{$0.key<$1.key}
pickerView.delegate = self
pickerView.dataSource = self
label.text = "id: " + String(dataSource[pickerView.selectedRow(inComponent: 0)].key) + " - " + "name: " + dataSource[pickerView.selectedRow(inComponent: 0)].value
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return dataSource.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return dataSource[row].value
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
label.text = "id: " + String(dataSource[row].key) + " - " + "name: " + dataSource[row].value
}
}
sample
Your approach of using a dictionary of your data structure is problematic. Dictionaries are, by design, unordered.
There are lots of ways to do this. Most involve creating an array of some sort containing items for each entry from which you want the user to pick.
For example, create an array of tuples:
typealias NameTuple = (id: Int, name: String)
var namesArray: [NameTuple]
Feed your picker view with the name field of each entry in your array. When the user selects an item, use the selected index to fetch that tuple and then get the ID.
You could also use an array of structs, or an array of name objects.
Let's say I have a stream of dogs private var dogs: Observable<[Dogs]>. Every time a new value is produced my block is called where I create a new dataSource and delegate for my UIPickerView and then within the block I call pickerView.reloadAllComponents() but my view appears with an empty pickerView, even though the dataSource and delegate are queried.
Example code:
self.dataStream
.subscribeNext {
self.dataSource = PickerViewDataSource(data: $0)
self.pickerView.dataSource = self.dataSource
self.delegate = PickerViewDelegate(data: $0, selectedRow: self._selectedRowStream)
self.pickerView.delegate = self.delegate
self.pickerView.reloadAllComponents()
}.addDisposableTo(self.disposeBag)
Debugging the dataSource and delegate I know these are queried and the reason I am keep dataSource and delegate reference in the UIViewController is due to the fact that the UIPickerView holds a weak reference for these.
This one of the last strategies I have tried and still get the same result. Any help would be appreciated. Thanks.
Update:
DogPickerViewDataSource:
class DogPickerViewDataSource: NSObject, UIPickerViewDataSource {
private var dogs: [Dog]
init(
dogs: [Dog]
) {
self.dogs = dogs
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return dogs.count
}
}
DogPickerViewDelegate:
class DogPickerViewDelegate: NSObject, UIPickerViewDelegate {
private var selectedRow: BehaviorSubject<Int>
private var dogs: [Dog]
init(
dogs: [Dog],
selectedRow: BehaviorSubject<Int>
) {
self.dogs = dogs
self.selectedRow = selectedRow
}
func pickerView(pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
let dogName = (self.dogs[row].name)!
return NSAttributedString(string: dogName)
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.selectedRow.onNext(row)
}
}
If I understand you correctly, on viewDidLoad you're initialising your pickerView with new delegate and data source, calling reloadAllComponents and expecting that everything should be displayed properly. But you're still displaying pickerView from previous view controller. You should try to hide picker view on previous view controller's viewWillDisappear method and display new one in viewDidAppear method of new view controller.
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 5 years ago.
Improve this question
What is the library to make drop down menu in swift? I am new to Xcode and the Swift language, so can anyone please direct me on how to implement the drop down list in swift?
(Swift 3) Add text box and uipickerview to the storyboard then add delegate and data source to uipickerview and add delegate to textbox. Follow video for assistance
https://youtu.be/SfjZwgxlwcc
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate {
#IBOutlet weak var textBox: UITextField!
#IBOutlet weak var dropDown: UIPickerView!
var list = ["1", "2", "3"]
public func numberOfComponents(in pickerView: UIPickerView) -> Int{
return 1
}
public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int{
return list.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
self.view.endEditing(true)
return list[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.textBox.text = self.list[row]
self.dropDown.isHidden = true
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == self.textBox {
self.dropDown.isHidden = false
//if you don't want the users to se the keyboard type:
textField.endEditing(true)
}
}
}
A 'drop down menu' is a web control / term. In iOS we don't have these. You might be better looking at UIPopoverController. Check out this tutorial for a bit of an insight to PopoverControllers
http://www.raywenderlich.com/29472/ipad-for-iphone-developers-101-in-ios-6-uipopovercontroller-tutorial
Unfortunately if you're looking to apply UIPopoverController in iOS9, you'll get a deprecated class warning. Instead you need to set your desired view's UIModalPresentationPopover property to achieve the same result.
Popover
In a horizontally regular environment, a presentation style
where the content is displayed in a popover view. The background
content is dimmed and taps outside the popover cause the popover to be
dismissed. If you do not want taps to dismiss the popover, you can
assign one or more views to the passthroughViews property of the
associated UIPopoverPresentationController object, which you can get
from the popoverPresentationController property.
In a horizontally compact environment, this option behaves the same as
UIModalPresentationFullScreen.
Available in iOS 8.0 and later.
Reference: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621355-modalpresentationstyle
You have to be sure to use UIPickerViewDataSource and UIPickerViewDelegate protocols or it will throw an AppDelegate error as of swift 3
Also please take note of the change in syntax:
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int
is now:
public func numberOfComponents(in pickerView: UIPickerView) -> Int
The following below worked for me.
import UIkit
class ViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {
#IBOutlet weak var textBox: UITextField!
#IBOutlet weak var dropDown: UIPickerView!
var list = ["1", "2", "3"]
public func numberOfComponents(in pickerView: UIPickerView) -> Int{
return 1
}
public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int{
return list.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
self.view.endEditing(true)
return list[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.textBox.text = self.list[row]
self.dropDown.isHidden = true
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == self.textBox {
self.dropDown.isHidden = false
//if you don't want the users to se the keyboard type:
textField.endEditing(true)
}
}
}
Using UIPickerview is the right way to go to implement it according to Apple's Human Interface Guidelines
If you select drop down in mobile safari it will show UIPickerview to let the use choose drop down items.
Alternatively
you can use UIPopoverController till iOS 9 as its deprecated but its better to stick with UIModalPresentationPopover of view you want o show as well
you can use UIActionsheet to show the items but it's better to use UIAlertViewController and choose UIActionSheetstyle to show as the former is deprecated in latest versions