public var body: some View {
VStack(alignment: .leading){
Text(text)
ForEach(0..<row, id: \.self){ row in
HStack{
ForEach(0..<col, id: \.self){ col in
GrassViewCell(
date: getDate(rowcol: [row, col], today: today),
color: blockColor,
inputLevel: getLevel(rowcol: [row, col])
){ date in
text = date
}
}
}
}
}
.padding()
.gesture(
DragGesture()
.onChanged{ gesture in
//how to recognize child view
//I just wanna childview's date by dragging cells
}
.onEnded{ _ in
text = formatter.string(from: today)
}
)
}
public struct GrassView: some View { //this is parent view
#State private var text //where i want to put child's date
}
I just wanna know childview's date by dragging on parent and put that date in text
.gesture(
DragGesture()
.onChanged{ gesture in
//how to recognize child view
//I just wanna know childview's date by dragging cells
}
.onEnded{ _ in
text = formatter.string(from: today)
}
)
is there any method that know childview by dragging??
i just tried attached .ontouchgesture() on childview but it didnt work
please help me..
enter image description here
You should be able to create a state variable in the parent view like so
#State var text : String = "some text"
and pass it into each child view as a binding
GrassViewCell(textToDisplay: $text)
and init at the top of GrassViewCell with #Binding var text : String. Then inside the gesture, just set text = whatever_date_in_child upon completion of the desired gesture.
Related
I am writing a SwiftUI iOS app where I need a Text view to automatically scroll to the end of its content whenever the content is updated. The update happens from the model. To not complicate this question with the details of my app, I have created a simple scenario where I have two text fields and a text label. Any text entered in the text fields is concatenated and shown in the text label. The text label is enclosed in a horizontal ScrollView and can be scrolled manually if the text is longer than the screen width. What I want to achieve is for the text to scroll to the end automatically whenever the label is updated.
Here is the simple model code:
class Model: ObservableObject {
var firstString = "" {
didSet { combinedString = "\(firstString). \(secondString)." }
}
var secondString = "" {
didSet { combinedString = "\(firstString). \(secondString)." }
}
#Published var combinedString = ""
}
This is the ContentView:
struct ContentView: View {
#ObservedObject var model: Model
var body: some View {
VStack(alignment: .leading, spacing: 10) {
TextField("First string: ", text: $model.firstString)
TextField("Second string: ", text: $model.secondString)
Spacer().frame(height: 20)
Text("Combined string:")
ScrollView(.horizontal) {
Text(model.combinedString)
}
}
}
}
From the research I have done, the only way I have found to scroll to the end of the text, without having to do it manually, is to add a button to the view, which causes the text in the label to scroll to the end.
Here is the above ScrollView embedded in a ScrollViewReader, with a button to effect the scrolling action.
ScrollViewReader { scrollView in
VStack {
ScrollView(.horizontal) {
Text(model.combinedString)
.id("combinedText")
}
Button("Scroll to end") {
withAnimation {
scrollView.scrollTo("combinedText", anchor: .trailing)
}
}
.padding()
.foregroundColor(.white)
.background(Color.black)
}
}
This works, provided the intention is to use a button to effect the scrolling action.
My question is: Can the scrolling action above be triggered whenever the model is updated, without the need to click a button.
Any help or pointers will be much appreciated.
Thanks.
I assume you wanted this:
ScrollViewReader { scrollView in
VStack {
ScrollView(.horizontal) {
Text(model.combinedString)
.id("combinedText")
}
.onChange(of: model.combinedString) { // << here !!
withAnimation {
scrollView.scrollTo("combinedText", anchor: .trailing)
}
}
}
}
ScrollViewReader is the solution you're looking for. You may need to play around with the value. Also you'll need to add the .id(0) modifier to your textview.
ScrollView {
ScrollViewReader { reader in
Button("Go to first then anchor trailing.") {
value.scrollTo(0, anchor: .trailing)
}
// The rest of your code .......
Im displaying data from server and need to display the options in radiobuttons. But default none of the button should be selected. I was able to display radiobuttons. But when a particular button is clicked, only its image should be changed. Though with my code all the other button images too change. I have gone through some references SwiftUI - How to change the button's image on click?, but couldn't get it work. As am a newbie to SwiftUI, strucked here.
struct ListView: View {
#State var imageName: String = "radio-off"
var body: some View {
List(vwModel.OpnChoice.ItemList) { opn in
VStack(alignment:.leading) {
ForEach(opn.Choices.indices, id: \.self) { row in
Button(action: {
print("\(opn.Choices[row].ChoiceId)")
self.imageName = "radio-On"
}) {
Image(imageName)
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 30, height: 30)
}
Text(opn.Choices[row].ChoiceName)
}
}
}
}
}
As you are working with indices anyway, add a #Published property to your view model which contains the selected index.
Then set the index in the button action. The redraw of the view sets the button at the selected index to the on-state and the others to the off-state.
As I don't know your environment this is a simplified stand-alone view model and view with SF Symbols images
class VMModel : ObservableObject {
#Published var selectedOption = -1
var numberOfOptions = 5
}
struct ListView: View {
#StateObject private var vwModel = VMModel()
var body: some View {
VStack(alignment:.leading) {
ForEach(0..<vwModel.numberOfOptions, id: \.self) { opn in
Button(action: {
vwModel.selectedOption = opn
print("index \(opn) selected")
}) {
Image(systemName: opn == vwModel.selectedOption ? "largecircle.fill.circle" : "circle")
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 30, height: 30)
}
}
}
}
}
Update:
Meanwhile you can use a Picker with the .radioGroup modifier
enum Choice {
case one, two, three
}
struct ListView: View {
#State private var choice : Choice = .one
var body: some View {
Picker(selection: $choice, label: Text("Select an option:")) {
Text("One").tag(Choice.one)
Text("Two").tag(Choice.two)
Text("Three").tag(Choice.three)
}.pickerStyle(.radioGroup)
}
}
At the moment you are storing a single #State property "imageName" for the entire list. And then when any of the buttons in the list are tapped you are changing that single property. Which will affect all the buttons.
I'd suggest removing this property and putting something into the viewModel.
You appear to have a view model with an array called vwModel.opnChoice.itemList.
There are multiple ways of making this work. You could stop a boolean in the itemList to say whether each item is selected or not. But, as you want it to work like radio buttons what might be better is to have a property on the vwModel like selectedItem.
The button could then do...
vwModel.selectedItemId = opn.choices[row].choiceId
Then you button Label would be...
Image(vsModel.selectedItemId == opn.choices[row].choiceId ? "radio-on" : "radio-off")
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 30, height: 30)
This would allow you to toggle the button when it is tapped and should turn the other buttons off.
I am creating a reusable gallery view for an app and am having difficulties when any picture is tapped it suppose to become full screen but only the first picture in the array is shown every time no matter the picture tapped. Below is my code, thanks.
import SwiftUI
struct ReusableGalleryView: View {
let greenappData: GreenAppNews
let gridLayout: [GridItem] = Array(repeating: GridItem(.flexible()), count: 3)
#State private var fullscreen = false
#State private var isPresented = false
var body: some View {
VStack{
ScrollView{
LazyVGrid(columns: gridLayout, spacing: 3) {
ForEach(greenappData.GreenAppGallery, id: \.self) { item in
Image(item)
.resizable()
.frame(width: UIScreen.main.bounds.width/3, height: 150)
.onTapGesture {
self.isPresented.toggle()
print(" tapping number")
}
.fullScreenCover(isPresented: $isPresented) {
FullScreenModalView( imageFiller: item)
}
.background(Color.gray.opacity(0.5))
}
}
.padding(.horizontal)
}
}
}
}
This is an example of the json data:
{
"id" : "1",
"GreenAppGallery" : [
"Picture-1",
"Picture-2",
"Picture-3",
"Picture-4",
"Picture-5",
"Picture-6",
"Picture-7",
"Picture-8",
"Picture-9",
"Picture-10"
]
},
fullScreenCover, like sheet tends to create this type of behavior in iOS 14 when using isPresented:.
To fix it, you can change to the fullScreenCover(item: ) form.
Not having all of your code, I'm not able to give you an exact version of what it'll look like, but the gist is this:
Remove your isPresented variable
Replace it with a presentedItem variable that will be an optional. Probably a datatype that is in your gallery. Note that it has to conform to Identifiable (meaning it has to have an id property).
Instead of toggling isPresented, set presentedItem to item
Use fullScreenCover(item: ) { presentedItem in FullScreenModalView( imageFiller: presentedItem) } and pass it your presentedItem variable
Move the fullScreenCover so that it's attached to the ForEach loop rather than the Image
Using this system, you should see it respond to the correct item.
Here's another one of my answers that covers this with sheet: #State var not updated as expected in LazyVGrid
I am trying to update the style of the HStack containing my text box when the Textbox is selected. In the example below, I want the text box to have a red border when selected, otherwise the border should be gray.
My issue is that the textbox seems to go through an intermediate transition that I don't want, which is the border is updated to red, but the keyboard doesn't pop up until I select the textbox again (The textbox moves up a bit and then goes back down). It seems that there is some issue with the ordering of how the view refresh happens.
#State private var text: String
#State private var textFieldSelected: Bool = false
var body: some View {
let stack = HStack {
TextField("Enter name", text: $text, onEditingChanged: {
(changed) in
textFieldSelected = changed
})
}
if (textFieldSelected) {
stack
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.red, lineWidth: 1))
} else {
stack
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray, lineWidth: 1))
}
}
Here's a video example of the existing behavior:
Make it even simpler by using ternary condition for the border and the issue won't appear
struct ContentView : View {
#State private var text: String = ""
#State private var textFieldSelected: Bool = false
var body: some View {
HStack {
TextField("Enter name", text: $text, onEditingChanged: {
(changed) in
textFieldSelected = changed
})
}
.overlay(RoundedRectangle(cornerRadius: 10).stroke(textFieldSelected ? Color.red : Color.gray, lineWidth: 1))
}
}
Tested on iPhone 8 Plus iOS 14
I have seen a lot of examples and tutorial of how to use an empty TextField for collecting new values, but none that shows how to use a TextField to edit a value.
In my use-case, I want the TextField to be prepopulated/prefilled with data from my viewmodel, then as user edits the data, a Save button should be enabled. In my form, I also have a navigationlink that leads to a sub-page where the user can select something from a list, and then be routed back to the form.
It behaves as described as long I use an empty field; the user can type something temporary in the field, navigate to the sub page, and the temp value is still like it was when he left.
struct TextFieldDemo: View {
var model:String // Actual a more complex view model
#State var editedValue:String = ""
var body: some View {
VStack(alignment: .leading, spacing: 20) {
Group{
Text("Some label")
TextField("Placeholder text", text: $editedValue)
}
Divider()
Text("Some navigation link to push in a page where " +
"the user can select something from a list and click back...")
// If the user starts to edit the textfield - follows a navigation link and comes back
// he should be able to continue edit the field where he left of - the text field should
// not have been reset to the original value.
Button(action: {
// Call some save function in the ViewModel
},label: {
Text("SAVE")
}
).disabled(model == editedValue)
}.onAppear(){
// I could have done something like:
// self.editedValue = model
// but it seems like this will fire if the user navigates into the described page and reset
// the TextField to the model value.
}
}
}
struct TextFieldDemo_Previews: PreviewProvider {
static var previews: some View {
TextFieldDemo(model: "The old value")
}
}
To initialize the text field with the value from your model, you need to define your own initializer and use the State(wrappedValue:) initializer for #State vars:
struct TextFieldDemo: View {
var model:String // Actual a more complex view model
#State var editedValue: String
init(model: String) {
self.model = model
self._editedValue = State(wrappedValue: model) // _editedValue is State<String>
}
var body: some View {
VStack(alignment: .leading, spacing: 20) {
Group{
Text("Some label")
TextField("Placeholder text", text: $editedValue)
}
Divider()
Text("Some navigation link to push in a page where " +
"the user can select something from a list and click back...")
// If the user starts to edit the textfield - follows a navigation link and comes back
// he should be able to continue edit the field where he left of - the text field should
// not have been reset to the original value.
Button(action: {
// Call some save function in the ViewModel
},label: {
Text("SAVE")
}
).disabled(model == editedValue)
}.onAppear(){
// I could have done something like:
// self.editedValue = model
// but it seems like this will fire if the user navigates into the described page and reset
// the TextField to the model value.
}
}
}
struct TextFieldDemo_Previews: PreviewProvider {
static var previews: some View {
TextFieldDemo(model: "The old value")
}
}
how about something like this test code. The key is to use the "ObservableObject":
import SwiftUI
class MyModel: ObservableObject {
#Published var model = "model1"
}
struct ContentView: View {
#ObservedObject var myModel = MyModel()
#State var editedValue = ""
var body: some View {
NavigationView {
VStack(alignment: .leading, spacing: 20) {
Group{
Text("Some label")
TextField("Placeholder text", text: Binding<String>(
get: { self.editedValue },
set: {
self.editedValue = $0
self.myModel.model = self.editedValue
})).onAppear(perform: loadData)
}
Divider()
NavigationLink(destination: Text("the nex page")) {
Text("Click Me To Display The next View")
}
// If the user starts to edit the textfield - follows a navigation link and comes back
// he should be able to continue edit the field where he left of - the text field should
// not have been reset to the original value.
Button(action: {
// Call some save function in the ViewModel
self.myModel.model = self.editedValue
},label: {
Text("SAVE")
})
}
}.navigationViewStyle(StackNavigationViewStyle())
}
func loadData() {
self.editedValue = myModel.model
}
}