Cannot invoke initializer for type 'TextField<_>' with propertyWrapper of UseDefaults - properties

I am working on a SwiftUI screen that updates multiple values in the UserDefaults, to allow the app to persist basic settings. I am trying to use Combine and SwiftUI, as this is a native WatchOS app.
The basic View is giving me an error that I believe has to do with the propertyWrapper for UserDefaults, but as I have never worked with propertyWrappers (or Combine for that matter) I am un able to figure out how to fix this.
here's the property wrapper:
import Foundation
#propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
UserDefaults(suiteName: "group.com.my.app")!.value(forKey: key) as? T ?? defaultValue
} set {
UserDefaults(suiteName: "group.com.my.app")!.set(newValue, forKey: key)
}
}
}
As you can see it wraps all the Key Pairs for the UserDefaults.
My class is similarly very simple, consisting of two bools and a double
import SwiftUI
import Foundation
import Combine
class Setup: ObservableObject {
private var notificationSubscription: AnyCancellable?
let objectWillChange = PassthroughSubject<Setup,Never>()
#UserDefault("keyOpt1Enabled", defaultValue: false)
var opt1Enabled: Bool {
willSet{
objectWillChange.send(self)
}
}
#UserDefault("keyOpt2Enabled", defaultValue: false)
var opt2Enabled: Bool {
willSet{
objectWillChange.send(self)
}
}
#UserDefault("keyValueDouble", defaultValue: Double(0.00))
var someValueDouble: Double {
willSet{
objectWillChange.send(self)
}
}
init() {
notificationSubscription = NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification).sink { _ in
self.objectWillChange.send(self)
}
}
}
The problem is that in SwiftUI I am using a TextField to allow for entering and updating the double value
#ObservedObject var setup: Setup = Setup()
private var currencyFormatter: NumberFormatter = {
let f = NumberFormatter()
f.numberStyle = .currency
return f
}()
var body: some View {
ScrollView{
HStack{
TextField(self.$setup.someValueDouble,
formatter: currencyFormatter,
placeholder: "0.00",
onEditingChanged: {_ in
print("editing changed")
},
onCommit: {
print("updated")
}
)
}
HStack{
Button(action: {
self.setup.opt1Enabled = false
self.setup.opt2Enabled = true
} ) {
Text(verbatim: "Opt 1")
.font(Font.system(size: 16, design: Font.Design.rounded))
}
.background(setup.opt1Enabled ? Color.blue : Color.gray)
.disabled(self.setup.opt1Enabled)
.cornerRadius(5)
Button(action: {
self.setup.opt1Enabled = true
self.setup.opt2Enabled = false
}) {
Text(verbatim: "Opt 2")
}
.background(setup.opt2Enabled ? Color.blue : Color.gray)
.disabled(self.setup.opt2Enabled)
}
}
}
}
The TextField then gives the message that the Generic parameter 'Label' could not be inferred. Xcode offers to "Fix" this but the end results is TextField which is obviously incomplete, but the only view in this whole program is "ContentView" which is invalid.

The problem is that TextField's initialiser takes first a String as it's placeholder and next the value it is bound to, so it should look roughly like this:
TextField("Placeholder",
value: self.$setup.someValueDouble,
formatter: currencyFormatter,
onEditingChanged: { _ in },
onCommit:{})
.keyboardType(.numberPad)
You might want to read more about TextFields and Formatter, because it seems to be a tricky subject.

Related

Kotlin parse 2 type of field(string, object) like on Swift

on IOS i have a code for parsing nested json. This json can contains string and object. It looks like this(Swift):
struct Place: Codable {
let value: [Dependent]?
}
enum Dependent: Codable {
case object(Place)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let objectVal = try container.decode(Place.self)
self = .object(objectVal)
} catch DecodingError.typeMismatch {
let stringVal = try container.decode(String.self)
self = .string(stringVal)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .object(let value):
try container.encode(value)
case .string(let value):
try container.encode(value)
}
}
}
But its to hard understand how i can create the same parser for Kotlin, i did code like this:
data class Place(
val value: List<Dependent>
)
sealed class Dependent {
data class OBJECT(val value: Place): Dependent()
data class STRING(val value: String): Dependent()
}
It doesnt work, i feel i missed something
UPD:
The josn looks like this:
{
"value": [
"123123123",
{"value": ["123123", "123123", {"value":["123"]}, "123"]}
]
}

Jetpack Compose Canvas drawPath doesn't work

I have this composable that is supposed to allow drawing with the pointer to a canvas but when I move the pointer nothing happens
#ExperimentalComposeUiApi
#Composable
fun CanvasDraw() {
val path = remember { Path() }
Canvas(modifier = Modifier
.fillMaxSize()
.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> {
path.moveTo(it.x, it.y)
}
MotionEvent.ACTION_MOVE -> {
path.lineTo(it.x, it.y)
}
else -> false
}
true
}) {
drawPath(
path = path,
color = Color.Blue,
style = Stroke(10f)
)
}
}
remember { Path() } is caching lambda content value for next recompositions, but it cannot trigger recomposition when content on this object is changed. To make some state, which will trigger recomposition on changes in Compose, you need to use a mutable state of some kind - it's a new thing made for Compose.
You can find more info about state in Compose in documentation, including this youtube video which explains the basic principles.
Storing points inside Path will not be clean, as it's not a basic type. Instead I'm using a mutable state list of points, like this:
val points = remember { mutableStateListOf<Offset>() }
Canvas(modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectDragGestures { change, _ ->
points.add(change.position)
}
}
) {
drawPath(
path = Path().apply {
points.forEachIndexed { i, point ->
if (i == 0) {
moveTo(point.x, point.y)
} else {
lineTo(point.x, point.y)
}
}
},
color = Color.Blue,
style = Stroke(10f)
)
}

Passing Variables between Views SwiftUI

Basic questions again:
I want to make the variable "anytext" visible and accessible for all future views I am going to add. In my case the variable is going to be a String.
Does the procedure change if it's a Float?
How can I save it as a Global variable?
Does the variable delete itself if I restart the app? And how do I save variables that will remain even after restarting the app?
import SwiftUI
struct Entry: View {
#State var anytext: String = ""
var body: some View {
VStack {
TextField("Enter text here", text: $anytext)
.padding()
.border(Color.black, width: 1)
.padding()
}
}
}
struct Entry_Previews: PreviewProvider {
static var previews: some View {
Entry()
}
}
Create & pass in an environment object at the root view of your app, or where any 'children' views may need access to anytext. Store anytext as a #Published property in that ObservedObject.
That's a pointer, but there will be lots of similar questions and stuff. Here is a HWS article which may help.
Here is an example:
class MyModel: ObservableObject {
#Published var anytext = ""
}
struct ContentView: View {
#StateObject private var model = MyModel()
var body: some View {
Entry().environmentObject(model)
}
}
struct Entry: View {
#EnvironmentObject private var model: MyModel
var body: some View {
VStack {
TextField("Enter text here", text: $model.anytext)
.padding()
.border(Color.black, width: 1)
.padding()
TextDisplayer()
}
}
}
struct TextDisplayer: View {
#EnvironmentObject private var model: MyModel
var body: some View {
Text(model.anytext)
}
}
Result:
All three views have model which they can access, to get the anytext property.
To answer your questions:
You use #Published because String, Float, Double etc are all value types.
Using environment objects, as shown here.
They are not persisted, see #AppStorage for saving that.

Kotlin: How to get a type of a method parameter

I know I can get the type of a method parameter by using "Method#parameters#name".
However, my parameters are all the subclass of A and I dont want to get the type A. I want to get the subclass name.
if (checkMethod(i)) {
val type = i.parameters[0].simpleName
if (!functions.containsKey(type)) {
functions[type] = HashMap()
}
if (!functions[type]?.containsKey(identifier)!!) {
functions[type]?.put(identifier, ArrayList())
}
functions[type]?.get(identifier)?.add(i)
}
Final Solution:
private fun analysis(clazz: KClass<EventHandler>, identifier: String) {
clazz.members.forEach {
if(it is KFunction) {
if(checkMethod(it)) {
val type = methodEventType(it)
if(!invokeMethods.containsKey(type)) invokeMethods[type] = HashMap()
if(!invokeMethods[type]!!.containsKey(identifier)) invokeMethods[type]!![identifier] = ArrayList()
invokeMethods[type]!![identifier]!!.add(it.javaMethod)
}
}
}
}
private fun checkMethod(method: KFunction<*>): Boolean {
method.annotations.forEach {
if(it is EventSubscriber) {
val type = method.parameters[1].type.classifier
if(type is KClass<*>) {
if(method.parameters.size == 2 && type.superclasses.contains(Event::class)) {
return true
}
}
}
}
return false
}
And notice here. I dont know why the method`s first parameter is allways a instance of its class. So the real parameter is start from 1 instead of 0.
Maybe you'll find this example useful (works with kotlin-reflect:1.4.21)
import kotlin.reflect.full.createType
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.jvm.reflect
open class A
val aType = A::class.createType()
class B: A()
class C: A()
val foo = { b: B, c: C ->
println(b)
println(c)
}
println(foo.reflect()!!.parameters[0].type.classifier == B::class) // true
println(foo.reflect()!!.parameters[1].type.classifier == C::class) // true
println(foo.reflect()!!.parameters[0].type.isSubtypeOf(aType)) // true
To get all subclasses
println((foo.reflect()!!.parameters[0].type.classifier as KClass<*>).allSuperclasses.contains(A::class)) // true
Try this to get the class of the first parameter:
i.parameters[0]::class.java

TornadoFX how to add validation while editing TableView

Consider folowing example:
class Item(name: String, number: Int) {
val nameProperty = SimpleStringProperty(name)
var name by nameProperty
val numberProperty by lazy { SimpleIntegerProperty(number) }
var number by numberProperty
}
class MainView : View("Example") {
val items = listOf(Item("One", 1), Item("Two", 2)).observable()
override val root = vbox {
tableview(items) {
column("Name", Item::nameProperty).makeEditable()
column("Number", Item::numberProperty).makeEditable(NumberStringConverter())
enableCellEditing()
}
}
}
How can I add a validator while editing cells? Is the only way to do that is to add rowExpander with some textfield and try to validate a model there?
You can either implement your own cellfactory and return a cell that shows a textfield bound to a ViewModel when in edit mode and an label if not. Alternatively, if you're fine with always displaying a textfield, you can use cellFormat and bind the current item to an ItemModel so you can attach validation:
class ItemModel(item: Item) : ItemViewModel<Item>(item) {
val name = bind(Item::nameProperty)
val number = bind(Item::numberProperty)
}
class MainView : View("Example") {
val items = listOf(Item("One", 1), Item("Two", 2)).observable()
override val root = vbox {
tableview(items) {
column("Name", Item::nameProperty).makeEditable()
column("Number", Item::numberProperty).cellFormat {
val model = ItemModel(rowItem)
graphic = textfield(model.number, NumberStringConverter()) {
validator {
if (model.number.value == 123) error("Invalid number") else null
}
}
}
}
}
}
It will look like this:
While it works, it's sort of wasteful since the nodes are recreated frequently. I would recommend approach number one if performance is a concern, until we get cellFragment support for TableView like we have for ListView.
EDIT: I implemented cellFragment support, so it's possible to create a more robust solution which will show a label when not in edit mode and a validating textfield when you enter edit mode.
class ItemModel : ItemViewModel<Item>() {
val name = bind(Item::nameProperty)
val number = bind(Item::numberProperty)
}
class MainView : View("Example") {
val items = listOf(Item("One", 1), Item("Two", 2)).observable()
override val root = vbox {
tableview(items) {
column("Name", Item::nameProperty).makeEditable()
column("Number", Item::numberProperty).cellFragment(NumberEditor::class)
}
}
}
class NumberEditor : TableCellFragment<Item, Number>() {
// Bind our ItemModel to the rowItemProperty, which points to the current Item
val model = ItemModel().bindToRowItem(this)
override val root = stackpane {
textfield(model.number, NumberStringConverter()) {
removeWhen(editingProperty.not())
validator {
if (model.number.value == 123L) error("Invalid number") else null
}
// Call cell.commitEdit() only if validation passes
action {
if (model.commit()) {
cell?.commitEdit(model.number.value)
}
}
}
// Label is visible when not in edit mode, and always shows committed value (itemProperty)
label(itemProperty) {
removeWhen(editingProperty)
}
}
// Make sure we rollback our model to avoid showing the last failed edit
override fun startEdit() {
model.rollback()
}
}
This will be possible starting from TornadoFX 1.7.9.