SwiftUI tracking "selected" (tapped on) TextField from an outside view - ios

I have a card view which contains a list of TextFields that are drawn from CoreData, something like:
var body: some View {
ZStack {
ForEach(textsArray, id:\.self) { text in
TextFieldView(textBlock: text, editing: editing)
.onTapGesture(count: 1) {
selected.selectedText = text
}
}
}
}
The textfield stores its contents and color in CoreData.
This card is displayed by an Edit view. The edit view also contains a ColorPicker, which should allow you to modify the color of the selected text field. So, if the user taps on a text field and starts editing it, a color picker will appear in the corner of their screen to allow them to modify the color of that field.
I attempted to create an observable object to keep track of the selected text field:
class Selection:ObservableObject {
#Published var selectedText : TextBlock?
}
Then my edit view would simply keep track of the selected text:
#ObservedObject var selected : Selection = Selection()
It also passes it down into the card view.
The issue is that the ColorPicker view requires a binding to a CGColor. I'm not sure how to pass this binding to the ColorPicker: I tried this:
ColorPicker("", selection: self.selected.selectedText.$color)
But XCode tells me there's no member of selected text called $color, which I guess is because color is #NSManaged rather than a #State property.
How can I pass a binding of the Color property to the color picker? Am I even approaching this the right way? I'm brand new to iOS development so I have no idea what the idiomatic ways of doing things are.

You can separate it into other view and call like
if self.selected.selectedText != nil {
MyColorPicker(selection: self.selected.selectedText!)
}
where
struct MyColorPicker: View {
#ObservedObject var selection: TextBlock
var body: some View {
ColorPicker("", selection: self.$selection.color)
}
}

Related

Is it possible to display non-formatted selected view in Picker inside the Form if the style is .navigationLink?

The code:
enum Size : String {
case s11
case s20
}
#State var selectedItem = Size.s11
var body: some View {
Form {
Section {
Picker("Font Size", selection: $selectedItem) {
Text("11").font(.system(size: 11)).tag(Size.s11)
Text("20").font(.system(size: 20)).tag(Size.s20)
}
.pickerStyle(.navigationLink)
}
}
}
Results in:
As you can see the number is formatted to be small. If there was no .navigationLink style this would be rendered as desired, but then the formatted text wouldn't be shown in the popup menu.
Is there some simple solution for this or I need to build a custom solution (like using a real NavigationLink instead of the Picker and then presenting a custom List and binding it all together)?

How to manually reload any row in swiftUI

I wanted to create List in SwiftUI with following requirements:
Each row has some detail, and Toggle which represents its isEnabled status.
If someone enables or disables that toggle, then based on some logic, Either I have to allow its to be enabled, OR not allow it to be enabled OR allow it to be enabled, but have to disable other object from other row.
I have my ViewModel and Model as below
I have my model and View model as somewhat like this
class MyViewModel:ObservableObject{
#Published myObjArray:[MyObject]
....
}
class MyObject:Identifiable{
var id:..
var isEnabled:Bool
var title:...
....
....
}
As MyObject is used at multiple places in project, so, I dont want to touch It.
For implementing Toggle, we have to provide binding, so, I created one state object in its RowView and on enable and disable of that toggle, I get its event like this:
struct MyRowView: View {
var myObj:MyObject
#State var isEnabled: Bool
....
....
init(myObj:MyObject) {
self.myObj = myObj
self.isEnabled = myObj.isEnabled
}
var body: some View {
....
Toggle("", isOn: $isEnabled)
.labelsHidden()
.onChange(of: isEnabled) { value in
self.MyViewModelAsEnvironmentObject.toggleValueUpdated()
}
....
}
As value of isEnabled changes, I am passing that to above mentioned MyViewModel object, where it checks for some conflicts, if conflicting, I am showing some alert, asking user to to forcefully enable this or not, if he says yes, then I have to disable row in other row, if user says no, then I have to disable this row.
Question is, How I can reload individual cell to enable or disable given toggle in given rows? If I print value directly in Row, then it is updating properly, but here isEnabled variable is assigned when row initiated, now its not possible to change its value from view model.
How to deal with this situation?

Change Text color on tap in SwiftUI

I have defined a List of Text inside SwiftUI in the following way.
struct SampleView: View {
private let list: [String] = ["Tapping each line should change its color from grey to black", "First Line", "Second Line", "Third Line."]
var body: some View {
ScrollViewReader { value in
List {
ForEach(Array(zip(list, list.indices)), id: \.0) { eachString, index in
Text(eachString)
.bold().font(.largeTitle)
.foregroundColor(.gray)
.onTapGesture {
self.foregroundColor(.black)
}
}
}.listStyle(.plain)
}.frame(maxHeight: .infinity)
}
}
struct SampleView_Preview: PreviewProvider {
static var previews: some View {
SampleView()
}
}
I need to be able to change the foregroundColor of a particular TextView whenever the user taps on it.
CustomString is a class that I defined that conforms to Hashable. I can't get it to conform to ObservableObject at the same time which means I can't change its value inside the onTapGesture block.
I also can't remove the index from the loop as I'll need that too.
TIA.
Don't take it personal at all, just keep in mind that Stackoverflow is a great way to share between developers, but all the basics are much more clear in the documentation and samples from Apple. They are incredibly well done by the way.
https://developer.apple.com/tutorials/swiftui
And for your problem, you must declare a state variable with your color, so when the color changes, the view is regenerated.
I have not tested your code, because you don't provide a copy/paste ready to run playground or view class, but you see the principle.
One sure thing is that if you need each text cell to update independently, you should make a "TextCell" subview or whatever, that has it's own color. Not at the highest level. In your code, I presume all cells will get the same color, since it is defined at top level of your List.
Happy new year :)
...
#State var color: Color = .gray
var myString: [CustomString]
...
ScrollViewReader { value in
List {
ForEach(Array(zip(myString, myString.indices)), id: \.0) { eachString, index in
Text(eachString.text)
.foregroundColor(color)
.onTapGesture {
self.color = .black
}
}
}

iOS Swift 5 Custom View (non-storyboards): how to create an update view - so that the view loads with the current value in the TextField

I am able to build a view with Swift 5 that will write data into my database. What I can't seem to figure out is how to build the "update" view.
My goal is to take all of the current values in the database and have them display inside of the TextField object. That way, the user starts with the current value in the field. Unfortunately, I can't figure out how to pre-wire the data into the TexField object when the view loads. Can someone please provide me with an example or a tutorial of how to accomplish this?
Here is my current code:
struct TripCreateModifyView: View {
#State private var pageTripName: String = ""
init(tripName: String)
{
self.pageTripName = tripName
}
var body: some View {
HStack { Text(" Trip Name:"); TextField("Enter Trip Name", text: $pageTripName) }
}
}

One view does not rerender when #State var changes based on Picker selection

Trying to build a simple MacOS app using SwiftUI. I have a View that contains a Picker bound to a State var. As a sanity check I have added a Text Views (the dwarves one and the volumes...itemName) that should also change with the Picker changes. And they do, but the View I want to rerender (the FileList) does not.
I suspect it has something to do with how I am trying to pass the new FileSystemItem (internal class) to the FileList. Like when the FilePanel rerenders the volumeSelection is back to 0 and the state is applied afterwards. So my problem is that I seem to be missing a fundamental concept on how this data is supposed to flow. I have gone through the WWDC info again and been reading other articles but I am not seeing the answer.
The desired behavior is changing the selection on the picker should cause a new FileSystemItem to be displayed in the FileList view. What is the right way to get this to happen? To state it more generically, how to you get a child view to display new data when a Picker selection changes?
struct FilePanel: View
{
#State var volumeSelection = 0
#State var pathSelection = 0
var volumes = VolumesModel() //should be passed in?
var dwarves = ["Schlumpy","Wheezy","Poxxy","Slimy","Pervy","Drooly"]
var body: some View {
VStack {
Picker(selection: $volumeSelection, label:
Text("Volume")
, content: {
ForEach (0 ..< volumes.count()) {
Text(self.volumes.volumeAtIndex(index: $0).itemName).tag($0)
}
})
FileList(item:volumes.volumeAtIndex(index: volumeSelection)).frame(minWidth: CGFloat(100.0), maxHeight: .infinity)
Text(dwarves[volumeSelection])
Text(volumes.volumeAtIndex(index: volumeSelection).itemName)
}
}
}
struct FileList: View {
#State var item : FileSystemItem
var body: some View {
VStack {
List(item.childItems){fsi in
FileCell(expanded:false, item: fsi)
}
Text(item.itemName)
}
}
}
#State is a state private to to the owning view, FileList will never see any change.
If VolumesModel was a simple struct turning FileList.item into a binding (in-out-state) may already work (the caller still needs to turn it's #State into a binding when passing it to the dependent using the $):
struct FileList: View {
#Binding var item : FileSystemItem
...
}
However, it feels as if VolumesModel was a more complex object having an array of members.
If this is the case the above will not suffice:
VolumesModel needs to be a class adopting ObservableObject
VolumesModel's important members need a bindable wrapper (#Published)
FileList.item should be turned into #ObservedObject (instead of #State or #Binding)
FilePanel.volumes also to be #ObservedObject wrapped
Hope that helps or at least points you into the right direction.

Resources