When I tap edit, it will show a delete button (minus icon). When the delete button tapped it will show the orange delete option (as the gif shows). Now I'm trying to reset the state back to the origin (from orange button back to video name and length ) when the Done button is tapped.
I'm trying few options like closures but nothing much.
Any help would be much appreciated!
My child view Video
struct Video: View {
var videoImage : String
var title : String
var duaration : Int
#Binding var deleteActivated : Bool
var body: some View {
HStack {
Image(videoImage)
...
if deleteActivated {
Button(action: {
}) {
ZStack {
Rectangle()
.foregroundColor(.orange)
.cornerRadius(radius: 10, corners: [.topRight, .bottomRight])
Text("Delete")
.foregroundColor(.white)
}
}
} else {
VStack(alignment: .leading){
....
My parent view VideosDirectory
struct VideosDirectory: View {
#State var videos:[DraftVideos] = [
DraftVideos(isSelected: true,title: "Superman workout", duration: 5, imageURL: "test"),
DraftVideos(isSelected: true,title: "Ironman workout", duration: 15, imageURL: "test1"),
DraftVideos(isSelected: true,title: "Ohman workout and long name", duration: 522, imageURL: "test2")
]
init() {
self._deleteActivated = State(initialValue: Array(repeating: false, count: videos.count))
}
#State private var deleteActivated: [Bool] = []
#State private var show = false
// #State private var editing = false
var body: some View {
// VStack {
NavigationView {
ScrollView(.vertical) {
VStack {
ForEach(videos.indices, id: \.self) { i in
HStack {
if self.show {
Button(action: {
withAnimation {
self.deleteActivated[i].toggle()
}
}) {
Image(systemName: "minus.circle.fill")
...
}
}
Video(videoImage: videos[i].imageURL, title: videos[i].title, duaration: videos[i].duration, deleteActivated: $deleteActivated[i])
}
}
}
.animation(.spring())
}
.navigationBarItems(trailing:
HStack {
Button(action: {
self.show.toggle()
}) {
if self.show {
Text("Done")
} else {
Text("Edit")
}
}
})
}
}
}
Provided code is not testable so just an idea:
Button(action: {
self.deleteActivated = Array(repeating: false, count: videos.count)
self.show.toggle()
}) {
or almost the same but as "post-action" in
}
.animation(.spring())
.onChange(of: self.show) { _ in
// most probably condition is not needed here, but is up to you
self.deleteActivated = Array(repeating: false, count: videos.count)
}
Related
Whenever a message is received from the backend, the app should automatically scroll to the beginning of the message. I cannot find a way for this to work. I've tried the stackoverflow solutions here, here and here to no avail.
Here's my code
struct ContentView: View {
#State private var inputText = ""
#State private var messages: [Message] = []
#State private var showThumbsDownSheet = false
#State private var feedbackText = ""
#State private var showThanksForFeedback = false
var body: some View {
VStack {
NavigationView {
ScrollViewReader { scrollView in
List {
ForEach(messages, id: \.id) { message in
Text(message.text)
if !message.isFromUser && message.isReplyFromBackEnd {
HStack {
ThumbsUpButton(message: message)
Spacer()
ThumbsDownButton(message: message, showThumbsDownSheet: $showThumbsDownSheet, reviewText: $feedbackText)
}
}
}
}
.onChange(of: messages) { value in
scrollView.scrollTo(self.messages.count - 1)
}
}
.navigationTitle("Ask Tais")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Menu {
Button(action: { exit(0) }) {
Label("Quit app", systemImage: "")
}
}
label: {
Label("Add", systemImage: "ellipsis.circle")
}
}
}
}
.onAppear {
// Add a "Hello" message to the list of messages when the app is first opened
self.messages.append(Message(id: UUID(), text: "Hello!", isFromUser: false, isReplyFromBackEnd: false, responseId: "0"))
}
.frame(maxHeight: .infinity)
HStack {
TextField("What do you want to say?", text: $inputText)
Button(action: {
self.messages.append(Message(id: UUID(), text: self.inputText, isFromUser: true, isReplyFromBackEnd: false, responseId:"0"))
sendRequest(with: self.inputText) { responseText, id in
self.messages.append(Message(id: UUID(), text: responseText, isFromUser: false, isReplyFromBackEnd: true, responseId: id))
}
self.inputText = ""
}) {
Text("Ask Tais")
}
}
.padding()
}
}
}
ScrollViewReader and scrollTo does not work on List with changing content. You have to use ScrollView instead of List and build your own list design.
well so the problem: when i open the EditViewScreen i need to get the title(-text) and body(-text) of that post. But I always get the title and body of the first cell.
then I didn’t understand the meaning of this post either: 2022-06-03 06:52:57.858609+0500 SwiftuiMVVM[4334:105229] [Presentation] Attempt to present <TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView: 0x127e734d0> on <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier_: 0x127e0ac70> (from <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentGS1_VVS_22_VariadicView_Children7ElementVS_24NavigationColumnModifier_GVS_18StyleContextWriterVS_19SidebarStyleContext__: 0x127e12bc0>) which is already presenting <TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView: 0x12a604820>.
HomeViewScreen()
ZStack {
List {
ForEach(viewModel.posts, id: \.self) { post in
PostCell(post: post).onLongPressGesture {
showingEdit.toggle()
}.sheet(isPresented: $showingEdit) {
EditViewScreen(post: post)
}
}
.onDelete(perform: delete)
}.listStyle(.plain)
if viewModel.isLoading {
ProgressView()
}
}
EditViewScreen()
import SwiftUI
struct EditViewScreen: View {
#ObservedObject var viewModel = EditViewModel()
#Environment(\.presentationMode) var presentation
var post: Post
#State var p_title = ""
#State var p_body = ""
func updatePost() {
let post = Post(id: post.id!, title: p_title, body: p_body)
viewModel.apiPostUpdate(post: post, handler: { isUpdated in
if isUpdated {
presentation.wrappedValue.dismiss()
}
})
}
var body: some View {
NavigationView {
ZStack {
VStack(spacing: 10) {
TextField("Edit title", text: $p_title)
.frame(height: 60)
.padding(.leading)
.background(.gray.opacity(0.1))
.cornerRadius(10)
.font(.system(size: 18))
TextField("Edit body", text: $p_body)
.frame(height: 60)
.padding(.leading)
.background(.gray.opacity(0.1))
.cornerRadius(10)
.font(.system(size: 18))
Button(action: {
updatePost()
}, label: {
Spacer()
Text("Update")
Spacer()
})
.padding()
.frame(height: 60)
.background(.black.opacity(0.7))
.foregroundColor(.white)
.cornerRadius(10)
.font(.system(size: 18))
Spacer()
}
.padding(.leading)
.padding(.trailing)
.padding(.top, -35)
if viewModel.isLoading {
ProgressView()
}
}
.navigationBarItems(leading: Button(action: {
presentation.wrappedValue.dismiss()
}, label: {
Text("Cancel")
.foregroundColor(.black)
.font(.system(size: 18, weight: .light))
}))
}.onAppear {
p_title = post.title!
p_body = post.body!
}
}
}
Well, some shortcomings have passed. I've made changes to the HomeViewModel().
class HomeViewModel: ObservableObject {
#Published var isLoading = false
#Published var posts = [Post]()
#Published var post = Post() // <-- here
func apiPostList() {
isLoading = true
AFHttp.get(url: AFHttp.API_POST_LIST, params: AFHttp.paramsEmpty(), handler: { response in
self.isLoading = false
switch response.result {
case .success:
let posts = try! JSONDecoder().decode([Post].self, from: response.data!)
self.posts = posts
case let .failure(error):
print(error)
}
})
}
}
HomeViewScreen()
struct HomeViewScreen: View {
#ObservedObject var viewModel = HomeViewModel()
#State private var showingEdit = false
var body: some View {
NavigationView {
ZStack {
List {
ForEach(viewModel.posts, id: \.self) { post in
PostCell(post: post)
.onLongPressGesture {
showingEdit.toggle()
viewModel.post = post // <-- here
}.sheet(isPresented: $showingEdit) {
EditViewScreen(post: viewModel.post)
// 'll be viewmodel.post, not just the post itself
}
}
.onDelete(perform: delete)
}
.listStyle(.plain)
if viewModel.isLoading {
ProgressView()
}
}
Thank you for your attention(awa #workingdog)
try this approach using #StateObject var viewModel in your HomeViewScreen,
and passing that to EditViewScreen. In addition use .sheet(item: ...)
outside the ForEach loop, as in the example code. Importantly never
use forced unwrapping, that is, do not use ! in your code at all, use if let ... or guard ....
struct HomeViewScreen: View {
#StateObject var viewModel = EditViewModel() // <-- here
#State var selectedPost: Post? // <-- here
var body: some View {
ZStack {
List {
ForEach(viewModel.posts, id: \.self) { post in
PostCell(post: post).onLongPressGesture {
selectedPost = post // <-- here
}
}
.onDelete(perform: delete)
}.listStyle(.plain)
// -- here
.sheet(item: $selectedPost) { post in
EditViewScreen(post: post, viewModel: viewModel) // <-- here
}
if viewModel.isLoading {
ProgressView()
}
}
}
}
struct EditViewScreen: View {
#ObservedObject var viewModel: EditViewModel // <-- here
#Environment(\.presentationMode) var presentation
var post: Post
#State var p_title = ""
#State var p_body = ""
//....
I'm trying to build an demo app by swiftUI that get multi text from user and add them to the list, below , there is an image of app every time user press plus button the AddListView show to the user and there user can add multi text to the List.I have a problem to add them to the list by new switUI data Flow I don't know how to pass data.(I comment more information)
Thanks 🙏
here is my code for AddListView:
import SwiftUI
struct AddListView: View {
#State var numberOfTextFiled = 1
#Binding var showAddListView : Bool
var body: some View {
ZStack {
Title(numberOfTextFiled: $numberOfTextFiled)
VStack {
ScrollView {
ForEach(0 ..< numberOfTextFiled, id: \.self) { item in
PreAddTextField()
}
}
}
.padding()
.offset(y: 40)
Buttons(showAddListView: $showAddListView)
}
.frame(width: 300, height: 200)
.background(Color.white)
.shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 10)
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
AddListView(showAddListView: .constant(false))
}
}
struct PreAddTextField: View {
// I made this standalone struct and use #State to every TextField text be independent
// if i use #Binding to pass data all Texfield have the same text value
#State var textInTextField = ""
var body: some View {
VStack {
TextField("Enter text", text: $textInTextField)
}
}
}
struct Buttons: View {
#Binding var showAddListView : Bool
var body: some View {
VStack {
HStack(spacing:100) {
Button(action: {
showAddListView = false}) {
Text("Cancel")
}
Button(action: {
showAddListView = false
// What should happen here to add Text to List???
}) {
Text("Add")
}
}
}
.offset(y: 70)
}
}
struct Title: View {
#Binding var numberOfTextFiled : Int
var body: some View {
VStack {
HStack {
Text("Add Text to list")
.font(.title2)
Spacer()
Button(action: {
numberOfTextFiled += 1
}) {
Image(systemName: "plus")
.font(.title2)
}
}
.padding()
Spacer()
}
}
}
and for DataModel:
import SwiftUI
struct Text1 : Identifiable , Hashable{
var id = UUID()
var text : String
}
var textData = [
Text1(text: "SwiftUI"),
Text1(text: "Data flow?"),
]
and finally:
import SwiftUI
struct ListView: View {
#State var showAddListView = false
var body: some View {
NavigationView {
VStack {
ZStack {
List(textData, id : \.self){ text in
Text(text.text)
}
if showAddListView {
AddListView(showAddListView: $showAddListView)
.offset(y:-100)
}
}
}
.navigationTitle("List")
.navigationBarItems(trailing:
Button(action: {showAddListView = true}) {
Image(systemName: "plus")
.font(.title2)
}
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ListView()
}
}
Because of the multiple-items part of the question, this becomes a lot less trivial. However, using a combination of ObservableObjects and callback functions, definitely doable. Look at the inline comments in the code for explanations about what is going on:
struct Text1 : Identifiable , Hashable{
var id = UUID()
var text : String
}
//Store the items in an ObservableObject instead of just in #State
class AppState : ObservableObject {
#Published var textData : [Text1] = [.init(text: "Item 1"),.init(text: "Item 2")]
}
//This view model stores data about all of the new items that are going to be added
class AddListViewViewModel : ObservableObject {
#Published var textItemsToAdd : [Text1] = [.init(text: "")] //start with one empty item
//save all of the new items -- don't save anything that is empty
func saveToAppState(appState: AppState) {
appState.textData.append(contentsOf: textItemsToAdd.filter { !$0.text.isEmpty })
}
//these Bindings get used for the TextFields -- they're attached to the item IDs
func bindingForId(id: UUID) -> Binding<String> {
.init { () -> String in
self.textItemsToAdd.first(where: { $0.id == id })?.text ?? ""
} set: { (newValue) in
self.textItemsToAdd = self.textItemsToAdd.map {
guard $0.id == id else {
return $0
}
return .init(id: id, text: newValue)
}
}
}
}
struct AddListView: View {
#Binding var showAddListView : Bool
#ObservedObject var appState : AppState
#StateObject private var viewModel = AddListViewViewModel()
var body: some View {
ZStack {
Title(addItem: { viewModel.textItemsToAdd.append(.init(text: "")) })
VStack {
ScrollView {
ForEach(viewModel.textItemsToAdd, id: \.id) { item in //note this is id: \.id and not \.self
PreAddTextField(textInTextField: viewModel.bindingForId(id: item.id))
}
}
}
.padding()
.offset(y: 40)
Buttons(showAddListView: $showAddListView, save: {
viewModel.saveToAppState(appState: appState)
})
}
.frame(width: 300, height: 200)
.background(Color.white)
.shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 10)
}
}
struct PreAddTextField: View {
#Binding var textInTextField : String //this takes a binding to the view model now
var body: some View {
VStack {
TextField("Enter text", text: $textInTextField)
}
}
}
struct Buttons: View {
#Binding var showAddListView : Bool
var save : () -> Void //callback function for what happens when "Add" gets pressed
var body: some View {
VStack {
HStack(spacing:100) {
Button(action: {
showAddListView = false}) {
Text("Cancel")
}
Button(action: {
showAddListView = false
save()
}) {
Text("Add")
}
}
}
.offset(y: 70)
}
}
struct Title: View {
var addItem : () -> Void //callback function for what happens when the plus button is hit
var body: some View {
VStack {
HStack {
Text("Add Text to list")
.font(.title2)
Spacer()
Button(action: {
addItem()
}) {
Image(systemName: "plus")
.font(.title2)
}
}
.padding()
Spacer()
}
}
}
struct ListView: View {
#StateObject var appState = AppState() //store the AppState here
#State private var showAddListView = false
var body: some View {
NavigationView {
VStack {
ZStack {
List(appState.textData, id : \.self){ text in
Text(text.text)
}
if showAddListView {
AddListView(showAddListView: $showAddListView, appState: appState)
.offset(y:-100)
}
}
}
.navigationTitle("List")
.navigationBarItems(trailing:
Button(action: {showAddListView = true}) {
Image(systemName: "plus")
.font(.title2)
}
)
}
}
}
I'm attempting to recreate the following animation from the Castro app...
(The GIF is slowed down so you can see the effect)
As you can see in the GIF above, you have a row of buttons that appear when the cell is tapped. Each button has a zoom-in and zoom-out effect. The animations are staggered, such that the first button finishes first and the last button finishes last.
What I've tried...
struct SwiftUIView: View {
#State var show: Bool = false
var body: some View {
VStack {
Button(action: {
withAnimation {
show.toggle()
}
}, label: {
Text("Button")
})
HStack {
if show {
Button(action: {}, label: { Image(systemName: "circle") })
.transition(.scale)
Button(action: {}, label: { Image(systemName: "circle") })
.transition(.scale)
Button(action: {}, label: { Image(systemName: "circle") })
.transition(.scale)
}
}
}
}
}
As you can see in the image above... Each button does zoom in, but only as the view is removed. Also, I don't know how to stagger the animation.
Try the following:
struct ContentView: View {
#State var show = false
#State var showArray = Array(repeating: false, count: 3)
var body: some View {
VStack {
Button(action: toggleButtons) {
Text("Button")
}
HStack {
ForEach(showArray.indices, id: \.self) { index in
self.circleView(for: index)
}
}
}
}
#ViewBuilder
func circleView(for index: Int) -> some View {
if show {
ZStack {
Image(systemName: "circle")
.opacity(.leastNonzeroMagnitude)
.animation(nil)
if showArray[index] {
Image(systemName: "circle")
.transition(.scale)
}
}
}
}
func toggleButtons() {
showArray.indices.forEach { index in
withAnimation {
self.show = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(index)) {
withAnimation {
self.showArray[index].toggle()
if index == self.showArray.count - 1, self.showArray[index] == false {
self.show = false
}
}
}
}
}
}
It uses a little hack to align the views correctly - in ZStack there is a fake Image with almost no opacity.
I am trying to create a multi-select list:
#Binding var selection:[String]
List {
ForEach(self.items, id: \.self) { item in
MultipleSelectionRow(title: item, isSelected: self.selection.contains(item)) {
if self.selection.contains(item) {
self.selection.removeAll(where: { $0 == item }) <=== NO AFFECT
}
else {
self.selection.append(item). <=== NO AFFECT
}
self.queryCallback()
}
}//ForEach
.listRowBackground(Color("TPDarkGrey"))
}//list
I have a row which is a button that calls the above action
struct MultipleSelectionRow: View {
var title: String
var isSelected: Bool
var action: () -> Void
var body: some View {
Button(action: self.action) {
HStack {
Text(self.title)
Spacer()
if self.isSelected {
Image(systemName: "checkmark")
}
}
.font(.system(size: 14))
}
}
}
Why does it not append or remote the item in the bound array? It seems to change on the second time through the view
I managed to produce an example from your code that works:
I don't know how the rest of your code is setup so I cannot hint you to anything unfortunately.
struct MultipleSelectionRow: View {
var title: String
var isSelected: Bool
var action: () -> Void
var body: some View {
Button(action: self.action) {
HStack {
Text(self.title)
Spacer()
if self.isSelected {
Image(systemName: "checkmark")
}
}
.font(.system(size: 14))
}
}
}
struct ContentView: View {
#State var selection:[String] = []
#State var items:[String] = ["Hello", "my", "friend", "did", "I", "solve", "your", "question", "?"]
var body: some View {
List {
ForEach(self.items, id: \.self) { item in
MultipleSelectionRow(title: item, isSelected: self.selection.contains(item)) {
if self.selection.contains(item) {
self.selection.removeAll(where: { $0 == item })
}
else {
self.selection.append(item)
}
}
}
.listRowBackground(Color("TPDarkGrey"))
}
}
}
I hope this helps to clarify things.