I want control the passed value to Binding for custom initializing, I am using this down code, xcode throw me this:
need help to solve the problem, thanks for help.
import SwiftUI
struct ContentView: View {
#State private var int: Int = Int()
var body: some View {
BindingView(int: $int)
}
}
struct BindingView: View {
#Binding var int: Int
init(int: Binding<Int>) {
if int == 0 { // <<: Here
_int = 100 // <<: Here
}
else {
_int = int
}
}
var body: some View {
Text(int.description)
}
}
You can't access Int directly like that for comparison, since it's a #Binding. Instead, you'll want to access its wrappedValue:
You're also running into a couple funny things just from naming your input parameter (int) the same as your stored property, but it's easily fixable. Here's one variation:
struct BindingView: View {
#Binding var int: Int
init(int: Binding<Int>) {
if int.wrappedValue == 0 { // <<: Here
int.wrappedValue = 100 // <<: Here
}
self._int = int
}
var body: some View {
Text(int.description)
}
}
Note that there's some tricky subtlety here: #Binding lets you directly access the underlying value, while with Binding<Int> you have to use the wrappedValue property.
Related
How can I refresh an environment var in SwiftUI? It is easy to update any object that's a part of an environment object, but it seems like there should be a way to re-initialize.
struct reinitenviron: View{
#EnvironmentObject private var globalObj: GlobalClass
var body: some View{
Text("refresh").onTapGesture {
globalObj = GlobalClass() //error here
}
}
}
The following gives an error that globalObj is get only. Is it possible to re-initialize?
A possible solution is to introduce explicit method in GlobalClass to reset it to initial state and use that method and in init and externally, like
class GlobalClass: ObservableObject {
#Published var value: Int = 1
init() {
self.reset()
}
func reset() {
self.value = 1
// do other activity if needed
}
}
struct reinitenviron: View{
#EnvironmentObject private var globalObj: GlobalClass
var body: some View{
Text("refresh").onTapGesture {
globalObj.reset() // << here
}
}
}
I have a view like this:
struct View1: View {
#Binding var myVariable: Bool
init() {
_myVariable = Binding.constant(true) // It works but myVariable is immutable, so I can't edit myVariable
}
init(myVariable: Binding<Bool>) {
_myVariable = myVariable
}
var body: some View {
Button("Change") {
myVariable.toggle()
}
}
}
struct View2: View {
var body: some View {
View1()
}
}
struct View3: View {
#State var myVariable = false
var body: some View {
View1(myVariable: $myVariable)
}
}
And I want to make this: If there is a parameter provided, set this to myVariable like second init in View1. Else, set the first value of myVariable like in first init.
I tried to use Binding.constant(value) but it is immutable. And I can't edit the variable. So, I need a mutable Binding initializer like Binding.constant(value). But I can't find it.
How can I solve this problem?
To avoid over-complicating View1, you can create an intermediate view with that name, and then have a private 'internal' view which has the actual implementation.
Code:
private struct View1Internal: View {
#Binding var myVariable: Bool
var body: some View {
Button("Change") {
myVariable.toggle()
}
}
}
struct View1: View {
private enum Kind {
case state
case binding(Binding<Bool>)
}
#State private var state = true
private let kind: Kind
init() {
kind = .state
}
init(myVariable: Binding<Bool>) {
kind = .binding(myVariable)
}
var body: some View {
switch kind {
case .state: View1Internal(myVariable: $state)
case .binding(let binding): View1Internal(myVariable: binding)
}
}
}
Here is my code.
Run the MainView struct, and click on the button which should update the word first to the word hello.
It does not update at all even though the logs show that the data is correctly updated. Therefore is there no way to get the view to update when a value changes inside an enum?
The only way I got it to work was a nasty hack. To try the hack just uncomment the 3 lines of commented code and try it. Is there a better way?
I looked at this similar question, but the same problem is there -> SwiftUI two-way binding to value inside ObservableObject inside enum case
struct MainView: View {
#State var selectedEnum = AnEnum.anOption(AnObservedObject(string: "first"))
// #State var update = false
var body: some View {
switch selectedEnum {
case .anOption(var value):
VStack {
switch selectedEnum {
case .anOption(let val):
Text(val.string)
}
TestView(object: Binding(get: { value }, set: { value = $0 }),
callbackToVerifyChange: callback)
}
// .id(update)
}
}
func callback() {
switch selectedEnum {
case .anOption(let value):
print("Callback function shows --> \(value.string)")
// update.toggle()
}
}
}
class AnObservedObject: ObservableObject {
#Published var string: String
init(string: String) {
self.string = string
}
}
enum AnEnum {
case anOption(AnObservedObject)
}
struct TestView: View {
#Binding var object: AnObservedObject
let callbackToVerifyChange: ()->Void
var body: some View {
Text("Tap here to change the word 'first' to 'hello'")
.border(Color.black).padding()
.onTapGesture {
print("String before tapping --> \(object.string)")
object.string = "hello"
print("String after tapping --> \(object.string)")
callbackToVerifyChange()
}
}
}
You need to declare your enum Equatable.
Creating a custom environment key works, but if I wish to set the value in a view, XCODE doesn't allow it. But the predefined environment values can be set. What am I doing wrong?
struct ResetDefault: EnvironmentKey {
static var defaultValue: Bool = false
}
extension EnvironmentValues {
var resetDefault: Bool {
get { self[ResetDefault.self] }
set { self[ResetDefault.self] = newValue }
}
}
struct ResetView: View {
#Environment(\.resetDefault) var reset
var body: some View {
Text("Reset").onAppear() {
reset = true. // Cannot assign to property: 'reset' is a get-only property
}
}
}
The Environment is used to pass values in parent > child direction, so value is set for usage. If you want to change internal of environment value then you need to wrap it somehow, possible variants are binding or reference type holder.
Here is an example of usage based on binding (similar to how .editMode and .presentationMode work)
struct TestResetEnv: View {
#State private var isActive = false
#State private var reset = false
var body: some View {
VStack {
Text("Current: \(reset ? "true" : "false")")
Button("Go") { self.isActive.toggle() }
if isActive {
ResetView()
}
}.environment(\.resetDefault, $reset) // set for children as env!!
}
}
struct ResetDefault: EnvironmentKey {
static var defaultValue: Binding<Bool> = .constant(false)
}
extension EnvironmentValues {
var resetDefault: Binding<Bool> {
get { self[ResetDefault.self] }
set { self[ResetDefault.self] = newValue }
}
}
struct ResetView: View {
#Environment(\.resetDefault) var reset
var body: some View {
Text("Reset").onAppear() {
self.reset.wrappedValue.toggle() // << change wrapped !!
}
}
}
I created a custom button, that shows a popover. Here is my code:
PopupPicker
struct PopupPicker: View {
#State var selectedRow: UUID?
#State private var showPopover = false
let elements: [PopupElement]
var body: some View {
Button((selectedRow != nil) ? (elements.first { $0.id == selectedRow! }!.text) : elements[0].text) {
self.showPopover = true
}
.popover(isPresented: self.$showPopover) {
PopupSelectionView(elements: self.elements, selectedRow: self.$selectedRow)
}
}
}
PopupSelectionView
struct PopupSelectionView: View {
var elements: [PopupElement]
#Binding var selectedRow: UUID?
var body: some View {
List {
ForEach(self.elements) { element in
PopupText(element: element, selectedRow: self.$selectedRow)
}
}
}
}
PopupText
struct PopupText: View {
var element: PopupElement
#Binding var selectedRow: UUID?
var body: some View {
Button(element.text) {
self.presentation.wrappedValue.dismiss()
self.selectedRow = self.element.id
}
}
}
That works fine, but can I create a custom event modifier, so that I can write:
PopupPicker(...)
.onSelection { popupElement in
...
}
I can't give you a full solution as I don't have all of your code and thus your methods to get the selected item anyhow, however I do know where to start.
As it turns out, declaring a function with the following syntax:
func `onSelection`'(arg:type) {
...
}
Creates the functionality of a .onSelection like so:
struct PopupPicker: View {
#Binding var selectedRow: PopupElement?
var body: some View {
...
}
func `onSelection`(task: (_ selectedRow: PopupElement) -> Void) -> some View {
print("on")
if self.selectedRow != nil {
task(selectedRow.self as! PopupElement)
return AnyView(self)
}
return AnyView(self)
}
}
You could theoretically use this in a view like so:
struct ContentView: View {
#State var popupEl:PopupElement?
var body: some View {
PopupPicker(selectedRow: $popupEl)
.onSelection { element in
print(element.name)
}
}
}
However I couldn't test it properly, please comment on your findings
Hope this could give you some insight in the workings of this, sorry if I couldn't give a full solution