I will try to explain my project, I develop an app on iPad.
First I have an HStack with a series of buttons which contain data, the button are green at the beginning and when we tap on it the button becomes red and the data contained in the button are stored in a variable.
Then, I have a second series of buttons which contain initial data, which must be replaced by other data (the data contained in the variable when we tap on a button which is in the HStack).
So we tap on the first button the data is recorded in a variable and we tap on a second button where we want the data.
The problem is that after that I want to delete the button contained in HStack this I can do it. But the red color stay on another button.
I suppose that it is because the "didTap" variable remains at true but how Can I "reset" this variable (becomes again false) when I tap on the second button.
Here is a simplified version of the code :
import SwiftUI
struct ContentView: View {
#State private var allWords : [String] = ["test1", "test2", "test3", "test4", "test5", "test6"] // contain the data to put in the buttons which are in the HStack
#State private var textInButton : String = "" //text temporarly stored
#State private var arrayIndice : Int = 0 //index temporarly stored
#State private var oneTap : Bool = false
#State private var isNight = false //unused at this moment
private var col : [GridItem] = Array(repeating: .init(.fixed(45)), count: 18)
let spaceOriginal = ["A1","A2","B1"]
#State var space = ["A1","A2","B1"]
var body: some View {
VStack{
ScrollView(.horizontal) {
HStack{
ForEach(allWords.indices, id: \.self) { index in
// display list of buttons ...
MyButton(myRow: allWords[index], oneTap: $oneTap, isNight: $isNight, textInButton: $textInButton, arrayIndice: $arrayIndice, arrayLocalIndice : index, allWords: $allWords)
}
}
.padding(.bottom, 30)
}
LazyVGrid(columns: col, spacing: 0) {
ForEach(0..<space.count) { i in
// display some button that I want to fill with the data contained in the buttons which are in the HStack
Button(action: {
// display again the original data when we click on the button and want to "reset" the button
if (space[i] != spaceOriginal[i]){
allWords.append(space[i])
space[i] = spaceOriginal[i]
}
// put data which are in the selected button (located in the hstack) in the button which I have to fill
if(textInButton != "" && space[i] == spaceOriginal[i]){
space[i] = textInButton
textInButton = ""
allWords.remove(at: arrayIndice)
oneTap = false
}
}, label: {
Text("\(space[i])")
.frame(width:50, height:75)
.font(.system(size: 20, weight: .medium))
.border(Color.black, width: 1)
})
}
}
}
}
}
struct ULD: View{
var title: String
var textColor: Color
var backgroundColor: Color
var body: some View{
Text(title)
.frame(width:75, height:75)
.background(backgroundColor)
.font(.system(size: 10, weight: .medium))
.foregroundColor(textColor)
.border(Color.black, width: 1)
}
}
struct MyButton: View {
#State private var didTap = false
var myRow: String
#Binding var oneTap: Bool
#Binding var isNight: Bool
#Binding var textInButton: String
#Binding var arrayIndice: Int
var arrayLocalIndice : Int
#Binding var allWords: [String]
var body: some View {
Button{
// reset the button if I tap again on a button located in the Hstack
if (didTap == true){
didTap.toggle()
oneTap = false
textInButton = ""
arrayIndice = 0
}
// tap on the button
else if (oneTap == false){
oneTap = true
didTap.toggle()
textInButton = myRow
arrayIndice = arrayLocalIndice
isNight.toggle()
}
} label:{
// By default the button is green, when we tap the first time on the button the didTap variable becomes true and the button becomes red, if we tap again it becomes green again
ULD(title: myRow, textColor: .black, backgroundColor: didTap ? .red : .green)
}
}
}
Here is some pictures to illustrate the problem :
Initial state :
When I click on test 2 it becomes red :
When I click on the position A2 the button is filled with test 2 but test 3 (the second position in the HStack remains red) :
Thank you very much
Related
I have a problem that I can't solve. I would like to change the background color of one button among others when the user taps on it.
So I made this code
ForEach(allWords, id: \.self) { myRow in
Button{
if (self.didTap == true){
self.didTap = false
}
else {
self.didTap = true
}
self.variableTitle = myRow
isNight.toggle()
} label:{
ULD(title: myRow, textColor: .black, backgroundColor: didTap ? .red : .green)
}
Where I retrieve a list of words in allWords and myRow contains at each time the title that I have on the button.
The problem is that when I tap on one button all buttons change their color but I would like to change only the color of one button that I have tapped.
Can you help me on this ?
Thank you
You need to have one variable didTap for each Button. This can be achieved by moving the button to a separate view.
You can create this view:
struct MyButton: View {
#State private var didTap = false // This will change the color
let myRow: String // I'm assuming it's a String, otherwise use the correct type
#Binding var isNight: Bool // This will change the variable in the parent view
#Binding var variableTitle: String // This will change the variable in the parent view (always assuming it's a String)
var body: some View {
Button{
didTap.toggle()
variableTitle = myRow
isNight.toggle()
} label:{
ULD(title: myRow, textColor: .black, backgroundColor: didTap ? .red : .green)
}
}
}
Then, in your parent view just call it as per the following example. Remember that isNight and variableTitle must both be a #State variable in the parent view.
ForEach(allWords, id: \.self) { myRow in
MyButton(myRow: myRow, isNight: $isNight, variableTitle: $variableTitle)
}
I hope this will help you,
struct ContentView: View {
#State var allWords = ["Button1","Button2","Button3","Button4"]
#State var allSelectedWords = Set<String>()
var body: some View {
ForEach(allWords, id: \.self) { myRow in
if #available(iOS 15.0, *) {
Button(action:{
if allSelectedWords.contains(myRow) {
allSelectedWords.remove(myRow)
} else {
allSelectedWords.removeAll()
allSelectedWords.insert(myRow)
}
}){
Text(myRow)
.foregroundColor(.black)
.background(allSelectedWords.contains(myRow) ? .red : .green)
}
} else {
// Fallback on earlier versions
}
}
}
}
I use matchedGeometryEffect and TapGesture to swap two elements with animated transition.
The animation works as expected but I want to change the zIndex of the elements for the duration of the animation.
I started from this example : https://www.appcoda.com/matchedgeometryeffect/
and adapted it to my needs :
struct MyView: View {
#State var colors = [Color.orange,Color.red,Color.yellow,Color.pink,Color.blue]
#State var target = Int.random(in: 0..<4)
#Namespace private var animationNamespace : Namespace.ID
var body: some View {
let columns: [GridItem] = Array(repeating: .init(.fixed(80)), count: 2)
LazyVGrid(columns: columns, spacing: 5){
ForEach(Array(self.colors.enumerated()), id: \.self.element) { indexedColor in
Element(color: indexedColor.element, animationNamespace: self.animationNamespace, action: {
self.colors.swapAt(target, indexedColor.offset)
target = Int.random(in: 0..<4)
})
}
}.animation(.linear)
}
}
struct Element: View {
let color : Color
let animationNamespace : Namespace.ID
let action: () -> Void
var body: some View {
Circle()
.fill(color)
.frame(width: 80, height: 80)
.matchedGeometryEffect(id: color, in: animationNamespace)
.onTapGesture {
action()
}
}
}
I want the animated elements to be always on top of the other elements.
The zIndex must be reseted at the end of the animation
Is this possible by using TapGesture ?
Xcode Version 12.0.1 (12A7300)
iOS 14.0
It is possible to put the clicked dot on the top level.. I changed your code here..
struct ContentView: View {
#State var colors = [Color.orange,Color.red,Color.yellow,Color.pink,Color.blue]
#Namespace private var animationNamespace : Namespace.ID
var body: some View {
HStack{
ForEach(Array(self.colors.enumerated()), id: \.self.element) { indexedColor in
Element(color: indexedColor.element, animationNamespace: self.animationNamespace, order: indexedColor.0, action: {
self.colors.swapAt(0, indexedColor.offset)
})
}
}.animation(.linear)
}
}
struct Element: View {
let color : Color
let animationNamespace : Namespace.ID
var order : Int
let action: () -> Void
var body: some View {
Circle()
.fill(color)
.frame(width: 80, height: 80)
.matchedGeometryEffect(id: color, in: animationNamespace)
.zIndex(Double(5-order))
.onTapGesture {
action()
}
}
}
Basically I just put the zIndex for each element based on their position. This means the first dot will have the highest zIndex. If you change the positions, the clicked one will get the highest zIndex and will lay on top of each other. I think it is not possible to put the other one on top aswell, as it will get the other zIndex, which was the lower one.
what I am trying to accomplish is have a loop of items where I am able to tap one and it gets bigger programmatically once tapped
here is my code and my results so far:
struct ContentView: View {
#State var emojisArray = ["📚", "🎹", "🎯", "💻"]
#State var selectedIndex = 0
var body: some View {
VStack {
ScrollView(.horizontal) {
HStack {
ForEach(0..<emojisArray.count) { item in
emojiView(emoji: self.emojisArray[item],
isSelected: item == self.selectedIndex ? true : false)
.onTapGesture {
print (item)
self.selectedIndex = item
}
}
}
}
.onAppear()
.frame(height:160)
VStack{
Text("selcted item:")
Text("\(self.emojisArray[self.selectedIndex])")
}
}
}
}
where emojiView is :
struct emojiView: View {
var emoji : String
#State var isSelected : Bool
var body: some View {
Text(emoji)
.font(isSelected ? .system(size: 120) : .system(size: 45))
}
}
I guess the problem is that the ScrollView does't reload itself
Just remove #State in emojiView
struct emojiView: View {
var emoji : String
var isSelected : Bool // << here !!
var body: some View {
Text(emoji)
.font(isSelected ? .system(size: 120) : .system(size: 45))
}
}
Tested with Xcode 12 / iOS 14
Update: I refactored it a bit to use #ObservableObject, but the issue still persists
In my SwiftUI app, I have a LazyVGrid populated with n cards and one New Event button. When I press the New Event button, I have a custom modal overlaying the View to let the user add a new event. However, after dismissing the view (by tapping a Create Event button), the modal disappears however the new event card isn't added to the LazyVGrid. It only appears once I force quit the app and re-open it.
Here is the main ContentView:
struct ContentView: View {
#State var progress: Double = 0.0
#State var editMode: Bool = false
#State var angle: Double = 0
#State var showModal: Bool = false
#ObservedObject var model: Model
var columns: [GridItem] = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
func animate(while condition: Bool) {
self.angle = condition ? -0.6 : 0
withAnimation(Animation.linear(duration: 0.125).repeatForever(while: condition)) {
self.angle = 0.6
}
if !condition {
self.angle = 0
}
}
var body: some View {
ZStack {
NavigationView {
LazyVGrid(columns: columns, spacing: 10) {
ForEach(model.events, id: \.self) { event in
SmallCardView(event: event, angle: $angle)
}
if !showModal {
AddEventButtonView(
editMode: $editMode,
showModal: $showModal,
namespace: namespace
)
} else {
Spacer().frame(height: 100)
}
}
.onLongPressGesture {
if !self.editMode {
self.editMode = true
animate(while: self.editMode)
}
}
}
if self.showModal {
AddEventView(namespace: namespace, events: self.$model.events, showModal: $showModal)
}
}
}
}
This is the View for the Add Event button:
struct AddEventButtonView: View {
#Binding var editMode: Bool
#Binding var showModal: Bool
var namespace: Namespace.ID
var body: some View {
Image(systemName: "calendar.badge.plus")
.frame(height: 100)
.frame(maxWidth: .infinity)
.opacity(self.editMode ? 0.0 : 1.0)
.onTapGesture {
withAnimation {
self.showModal = true
}
}
}
}
and this is my modal view:
struct AddEventView: View {
#State var eventName: String = ""
#State var endDate = Date()
#State var gradientIndex: Int = 0
#Binding var showModal: Bool
#Binding var events: [Event]
var namespace: Namespace.ID
init(namespace: Namespace.ID, events: Binding<[Event]>, showModal: Binding<Bool>) {
self.namespace = namespace
self._events = events
self._showModal = showModal
}
var body: some View {
VStack(spacing: 30.0) {
//...
Button(action: {
let event = Event(name: eventName, start: .init(), end: endDate, gradientIndex: gradientIndex)
self.events.append(event)
withAnimation {
self.showModal = false
}
}) {
RoundedRectangle(cornerRadius: 13)
.frame(maxWidth: .infinity)
.frame(height: 42)
.overlay(
Text("Add Event")
.foregroundColor(.white)
.bold()
)
}
.disabled(self.eventName.isEmpty)
}
}
}
If anyone has any idea of how to update the LazyVGrid to show the new event card when a new event is added that'd be awesome, thanks!
Here's the problem:
It is not clear how you did set up model, but it should be observed to update view, as (scratchy)
struct ContentView: View {
#State var progress: Double = 0.0
#State var editMode: Bool = false
#State var angle: Double = 0
#State var showModal: Bool = false
#ObservedObject var model: Model // << here !!
...
or use #EnvironmentObject pattern with injection via .environmentObject
or for Swift 2.0 use
#StateObject var model: Model = Model.shared
In DoctorHomePage I have a grouped list and above the list I want to add a text view, but the text view doesn't show only if I change the font to a bigger one, but it is too big and I want it smaller. Here is my code:
import SwiftUI
import Combine
struct DoctorHomePage: View {
#Binding var shouldPopToRootView : Bool
#State private var curent: Int? = nil
#State private var isActive: Bool = false
#State private var id = 0
let defaults = UserDefaults.standard
let networkRequest = Network()
#State var cancelable: AnyCancellable? = nil
#State var localPatients : [Patients] = []
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: ContentView(), tag: 1, selection: $curent) {
EmptyView()
}
Text("Welcome, doctor!") // this is the text that I want to add
.font(.system(size: 30)).fontWeight(.ultraLight)
.padding(.top, 50)
// PATIENT LIST
List(localPatients) { patient in
VStack(alignment: .leading) {
Text(patient.name)
}
}.listStyle(GroupedListStyle())
.onAppear(perform: {
self.loadPatients()
connCode = self.defaults.integer(forKey: "doctorID")
self.id = connCode
})
}.edgesIgnoringSafeArea([.top, .bottom])
}.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
}
}
Here are some screen shots to help you understand the problem:
The first image is with no text view.
The second image is with the font size of 60.
The third image is with the font size of 30.
Seems like some strange / buggy behavior.
Setting the zIndex of you welcome text will fix your problem.
Text("Welcome, doctor!").zIndex(1)