I'm studying the basics of TornadoFX for Kotlin.
I have this code:
class MainView : View() {
override val root = vbox()
init {
with(root) {
datepicker {
value = LocalDate.now()
}
button("Choose date") {
textFill = Color.GREEN
action {
println("Button pressed!")
}
}
}
}
}
when the button is pressed, I would like to take the date chosen by the user.
how can I do?
One solution would be to have a LocalDate property bound to the DatePicker, like this:
class MainView : View() {
private val dateProperty = SimpleObjectProperty<LocalDate>()
override val root = vbox()
init {
with(root) {
datepicker(dateProperty) {
value = LocalDate.now()
}
button("Choose date") {
textFill = Color.GREEN
action {
val dateValue = dateProperty.value
println("Button pressed!")
}
}
}
}
}
The other solution would be to have the DatePicker instance in your class, and then take the value from it, like this:
class MainView : View() {
private var datePicker : DatePicker by singleAssign()
override val root = vbox()
init {
with(root) {
datePicker = datepicker {
value = LocalDate.now()
}
button("Choose date") {
textFill = Color.GREEN
action {
val dateValue = datePicker.value
println("Button pressed!")
}
}
}
}
}
Additionally, you can implement a ViewModel, to separate UI and logic, see: Editing Models and Validation
Also, the style of your code can be improved: you can work directly with the VBox, like this:
class MainView : View() {
override val root = vbox {
datepicker {
value = LocalDate.now()
}
button("Choose date") {
textFill = Color.GREEN
action {
println("Button pressed!")
}
}
}
}
Related
I would like to show an ActionSheet containing InApp purchase objects the user can purchase.
But I want that sheet to contain the prices of such objects, like:
Object 1 ($1.99)
Object 2 ($2.99)
...
but the price is asynchronous, cause it has to be retrieved from the store.
So, I thought about doing this:
struct Package {
enum Packtype:String {
typealias RawValue = String
case obj1 = "com.example.object1"
case obj2 = "com.example.object2"
}
var productID:String = ""
#State var namePriceString:String = ""
init(productID:String) {
self.productID = productID
}
}
then, when I create the action sheet button I do this:
var obj1 = Package(productID: Package.Packtype.obj1.rawValue)
var obj2 = Package(productID: Package.Packtype.obj2.rawValue)
self.getPrices(packages:[obj1, obj2])
let obj1Button = ActionSheet.Button.default(Text(obj1.$namePriceString)) {
// do something with obj1
}
let obj2Button = ActionSheet.Button.default(Text(obj2.$namePriceString)) {
// do something with obj1
}
// build the actionsheet
later in the code:
func getPrices(packages:[Package]) {
let productIDS = Set(packages.map {$0.productID})
SwiftyStoreKit.retrieveProductsInfo(productIDS) { (answer) in
if answer.invalidProductIDs.first != nil { return }
let results = answer.retrievedProducts
if results.count == 0 { return }
for result in answer {
if let package = packages.filter({ ($0.productID == result.productIdentifier) }).first {
package.namePriceString = result.localizedTitle + "(" + "\(result.localizedPrice!)" + ")"
}
}
}
}
I have an error pointing to Text on the button creation lines saying
Initializer 'init(_:)' requires that 'Binding' conform to
'StringProtocol'
In a nutshell I need this:
I display the actionsheet. Its buttons contain no price.
I retrieve the prices
Actionsheet buttons are updated with the prices.
A possible solution is to return prices in a completion handler and only then display the action sheet:
struct ContentView: View {
#State var showActionSheet = false
#State var localizedPrices = [Package: String]()
var body: some View {
Button("Get prices") {
getPrices(packages: Package.allCases, completion: {
localizedPrices = $0
showActionSheet = true
})
}
.actionSheet(isPresented: $showActionSheet) {
let buttons = localizedPrices.map { package, localizedPrice in
ActionSheet.Button.default(Text(localizedPrice), action: { buy(package: package) })
}
return ActionSheet(title: Text("Title"), message: Text("Message"), buttons: buttons + [.cancel()])
}
}
}
func getPrices(packages: [Package], completion: #escaping ([Package: String]) -> Void) {
// simulates an asynchronous task, should be replaced with the actual implementation
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
let localizedPrices = Dictionary(uniqueKeysWithValues: packages.map { ($0, "\(Int.random(in: 1 ..< 100))") })
completion(localizedPrices)
}
}
func buy(package: Package) {
print("Buying \(package.rawValue)")
}
enum Package: String, CaseIterable {
case obj1 = "com.example.object1"
case obj2 = "com.example.object2"
}
This can be further tuned with loading animations etc...
So I have a swift view as where the minimum example is something as follows (It is a UIView but for simplicity sake I'm going to make it a SwiftUI view):
class ViewName: UIView {
#State var time: String = ""
func setTime() {
for place in self.data.places {
print("the place address is \(place.address) and the representedobject title is \((representedObject.title)!!)")
if (self.representedObject.title)!! == place.address {
print("there was a match!")
print("the time is \(place.time)")
self.time = place.time
print("THE TIME IS \(self.time)")
}
}
print("the final time is \(self.time)")
}
var body: some View {
//setTime() is called in the required init() function of the View, it's calling correctly, and I'm walking through my database correctly and when I print place.time, it prints the correct value, but it's the assignment self.time = place.time that just doesn't register. If I print place.time after that line, it is just the value ""
}
}
Reference type is not allowed to be a SwiftUI View. We cannot do the following:
class ViewName: UIView, View {
...
}
, so probably you meant this
struct ViewName: View {
// ... other properties
#State var time: String = ""
func setTime() {
for place in self.data.places {
if self.representedObject.title == place.address {
self.time = place.time
}
}
}
var body: some View {
Text("Some View Here")
.onAppear {
self.setTime() // << here !!
}
}
}
I have this code in an MVVM code in SwiftUI. My Objective is when the app loads for the first time to return the result without the filter. When I press the button on the view to trigger CatLotViewModel to reload the filtered data but can't seem to figure out I can trigger it.
class CatLotViewModel: ObservableObject {
//MARK: - Properties
#Published var catViewModel = [CatViewModel]()
private var cancellabels = Set<AnyCancellable>()
init() {
MyAPIManager().$cat.map{ kitten in
// Filter
let filtered = kitten.filter{ ($0.meals.contains(where: {$0.feed == false}))}
return filtered.map { park in
MyCatViewModel(parking: park)
}
// return kitten.map { park in
// CatViewModel(parking: park)
// }
}
.assign(to: \.catViewModel, on: self)
.store(in: &cancellabels)
}
}
Add a load function to your view model with isFiltered as a parameter. Call the function when pressing a button.
struct ContentView: View {
#ObservedObject var catLotViewModel = CatLotViewModel()
var body: some View {
Button(action: { self.catLotViewModel.load(isFiltered: true) }) {
Text("Reload")
}
}
}
class CatLotViewModel: ObservableObject {
//MARK: - Properties
#Published var catViewModel = [CatViewModel]()
private var cancellabels = Set<AnyCancellable>()
init() {
loadData(isFiltered: false)
}
func loadData(isFiltered: Bool) {
MyAPIManager().$cat.map{ kitten in
if isFiltered {
let filtered = kitten.filter{ ($0.meals.contains(where: {$0.feed == false}))}
return filtered.map { park in
MyCatViewModel(parking: park)
}
} else {
return kitten.map { park in
CatViewModel(parking: park)
}
}
.assign(to: \.catViewModel, on: self)
.store(in: &cancellabels)
}
}
I'm Korean android developer and new at Swift.
I am migrating my android app to ios, but meet a problem with interface and listener. I don't know how to implement listener to communicate between a custom view and a view controller.
I have a custom view(i.e. MyView) that has two buttons and each has own function.
In Android(with Java), I usually make an listener Interface in MyView and assign two functions inside like void func1( String val1 ) and func2...
public class MyView extends RelativeLayout {
private Button b1, b2;
private String val1, val2;
public interface OnButtonListener {
void onFunc1(String val1);
void onFunc2(String val2);
}
private OnButtonListener onButtonListener;
public void setOnButtonListener( OnButtonListener onButtonListener ) {
this.onButtonListener = onButtonListener;
}
public MyView( Context context, AttributeSet attrs ) {
super( context, attrs );
b1.setOnClickListener( view -> {
if (onButtonListener != null){
onButtonListener.onFunc1( val1 );
}
} );
b2.setOnClickListener( view -> {
if (onButtonListener != null){
onButtonListener.onFunc1( val2 );
}
} );
}
}
public class MyActivity extends Activity {
MyView myView1, myView2;
#Override
protected void onCreate( #Nullable Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
myView1.setOnButtonListener( new MyView.OnButtonListener() {
#Override
public void onFunc1( String val1 ) {
Log.d(TAG, val1);
}
#Override
public void onFunc2( String val2 ) {
Log.d(TAG, val2);
}
} );
myView2.setOnButtonListener( new MyView.OnButtonListener() {
#Override
public void onFunc1( String val1 ) {
// do something1
}
#Override
public void onFunc2( String val2 ) {
// do something2
}
} );
}
}
This code works perfectly as I wanted. So I've tried to apply same pattern into Swift, but I couldn't find any way to do.
below is for swift 4
import Foundation
import UIKit
import SnapKit
protocol OnButtonListener {
func onButton1( _ val1: String )
func onButton2( _ val2: String )
}
class MyView: UIView {
var onButtonListener: OnButtonListener?
var val1 = "abc"
var val2 = "123"
override init( frame: CGRect ) {
super.init( frame: frame )
let b1 = UIButton()
let b2 = UIButton() // i'm using snapkit
b1.addTarget(self, action: #selector(onB1), for: .touchUpInside)
b2.addTarget(self, action: #selector(onB2), for: .touchUpInside)
}
#objc func onB1() {
if onButtonListener != nil {
onButtonListener!.onButton1(val1 )
}
}
#objc func onB2() {
if onButtonListener != nil {
onButtonListener!.onButton2(val2 )
}
}
}
class MyVC : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let myView1 = MyView()
let myView2 = MyView()
myView1.onButtonListener = {
// ???
}
myView2.onButtonListener = {
// ???
}
}
}
I don't know how to implement listener in ViewContorller. I've tried same way as Kotlin but I didn't work too. Thank you for reading.
You have to set delegate in your viewcontroller and implement protocol methods in your viewcontroller
class MyVC : UIViewController, OnButtonListener {
override func viewDidLoad() {
super.viewDidLoad()
let myView1 = MyView()
myView1. onButtonListener = self
let myView2 = MyView()
myView2. onButtonListener = self
}
func onButton1( _ val1: String ) {
print(val1)
}
func onButton2( _ val2: String ) {
print(val2)
}
}
**Method 2: ** You can use block as well
class MyView: UIView {
var buttonAction : ((_ value : String) -> Void)? = nil
//.... your code
#objc func onB1() {
if let action = buttonAction {
action("abc")
}
}
#objc func onB2() {
if let action = buttonAction {
action("xyz")
}
}
}
In you ViewController
override func viewDidLoad() {
super.viewDidLoad()
let myView1 = MyView()
myView1.buttonAction = { value in
print(value)
}
}
Update your view controller code as follows:
First confirm your OnButtonListener protocol to UIViewController, and implement protocol method in your view controller.
class MyVC : UIViewController, OnButtonListener {
override func viewDidLoad() {
super.viewDidLoad()
let myView1 = MyView()
// Confirm protocol implementation in current view controller
myView1.onButtonListener = self
let myView2 = MyView()
// Confirm protocol implementation in current view controller
myView2.onButtonListener = self
}
func onButton1( _ val1: String) {
// your code
}
func onButton2( _ val2: String) {
// your code
}
}
Well, I am really confused since when I change the parameters and try to start()/ bypass() a node with iBAction, but nothing happens. The oscillator(WhiteNoise) seems to work properly while the Eq doesn't. Those parameters do have changed, however, the sound remained unchanged.
and here are my codes:
import AudioKit
class Conductor {
private var oscillator = AKWhiteNoise()
public var filter = AKEqualizerFilter()
private var gain = -12.0
init() {
oscillator.amplitude = 1
oscillator.stop()
filter = AKEqualizerFilter(oscillator)
filter.bypass()
AudioKit.output = filter
do {
try AudioKit.start()
} catch {
AKLog("AudioKit did not start!")
}
}
func play() {
if oscillator.isPlaying {
oscillator.stop()
} else {
oscillator.play()
}
}
func bypass(centerFrequency: Double, Q: Double) {
if filter.isBypassed {
filter.rampDuration = 0.3
filter.centerFrequency = centerFrequency
filter.bandwidth = centerFrequency/Q
filter.gain = pow(10, gain/20)
filter.start()
} else {
filter.bypass()
}
print(filter.isBypassed)
}
}
and my calls in ViewController:
#IBAction func bypass(_ sender: Any) {
conductor.bypass(centerFrequency: 125, Q: 7)
}