onMove with Multiarray in SwiftUI - swiftui

i need a little extra help with the onMove modifier and a multi array.
Is there a way to use the onMove modifier with a multiarray?
Thanks in advance!
#State var multiArray = [["String_1"],["String_2"]]
#State var edit: Bool = false
var body: some View {
NavigationView{
List {
Section{
Button {
edit.toggle()
} label: {
Text("Edit")
}.buttonStyle(BorderlessButtonStyle())
}
ForEach(0..<multiArray.count, id:\.self ){ i in
ForEach(multiArray[i], id:\.self ){ string in
Text(string)
}
}.onMove(perform: move2)
}.environment(\.editMode, .constant(edit ? EditMode.active : EditMode.inactive))
}
}
func move2(from source: IndexSet, to destination: Int) {
multiArray.move(fromOffsets: source, toOffset: destination)
}

Related

Swiftui remove swipe to delete functionality

Is there a way to remove or deactivate swipe to delete functionality that remove items only per edit button?
You can limit the delete functionality of a List/Form depending on the EditMode state, by using deleteDisabled(_:).
The following is a short example demonstrating deleting which only works in edit mode:
struct ContentView: View {
#State private var data = Array(1 ... 10)
var body: some View {
NavigationView {
Form {
DataRows(data: $data)
}
.navigationTitle("Delete Test")
.toolbar {
EditButton()
}
}
}
}
struct DataRows: View {
#Environment(\.editMode) private var editMode
#Binding private var data: [Int]
init(data: Binding<[Int]>) {
_data = data
}
var body: some View {
ForEach(data, id: \.self) { item in
Text("Item: \(item)")
}
.onMove { indices, newOffset in
data.move(fromOffsets: indices, toOffset: newOffset)
}
.onDelete { indexSet in
data.remove(atOffsets: indexSet)
}
.deleteDisabled(editMode?.wrappedValue != .active)
}
}

How can I check a if screen is on the navigation stack?

Given this...
NavigationLink(destination: Text("Hello")) {
Text("Press")
}
And this...
.sheet(isPresented: $viewModel.showComplete) {
Text("Hello")
}
How can I make the sheet only open if a view opened by the NavigationLink doesn't currently exist?
You may access the isActive parameter of NavigationLink and use it in a custom binding to determine whether to open the sheet.
Here is a simple demo:
struct ContentView: View {
#State var showSheet = false
#State var linkActive = false
var binding: Binding<Bool> {
.init(get: {
showSheet && !linkActive
}, set: {
showSheet = $0
})
}
var body: some View {
NavigationView {
VStack {
NavigationLink(
destination: DetailView(showSheet: $showSheet),
isActive: $linkActive
) {
Text("Go to...")
}
Button("Open sheet") {
self.showSheet.toggle()
}
}
}
.sheet(isPresented: binding) {
Text("Hello")
}
}
}
struct DetailView: View {
#Binding var showSheet: Bool
var body: some View {
Button("Open sheet") {
self.showSheet.toggle()
}
}
}

SwiftUI: Conditional onDelete

I am trying to create a list that only allows users to delete after entering an editing mode. I attempted to try using ternary operation in the onDelete modifier but was unable to figure it out. Any recommendations?
Here is my code:
struct ContentView: View {
#State private var stuff = ["First", "Second", "Third"]
#State private var check = false
var body: some View {
Form {
Button(action: { check.toggle() }, label: { Text(check ? "Editing" : "Edit") })
ForEach(0..<stuff.count) { items in
Section{ Text(stuff[items]) }
}
.onDelete(perform: self.deleteItem)
}
}
private func deleteItem(at indexSet: IndexSet) {
self.stuff.remove(atOffsets: indexSet)
}
}
I assume you look for the following
var body: some View {
Form {
Button(action: { check.toggle() }, label: { Text(check ? "Editing" : "Edit") })
ForEach(0..<stuff.count) { items in
Section{ Text(stuff[items]) }
}
.onDelete(perform: self.deleteItem)
.deleteDisabled(!check) // << this one !!
}
}
struct ContentView: View {
#State private
var stuff = ["First", "Second", "Third"]
var body: some View {
NavigationView {
Form {
ForEach(0..<stuff.count) { item in
Section {
Text(stuff[item])
}
}
.onDelete(
perform: delete)
}
.navigationBarItems(
trailing:
EditButton()
)
.navigationTitle("Test")
}
}
}
extension ContentView {
private func delete(at indexSet: IndexSet) {
stuff.remove(atOffsets: indexSet)
}
}
This can be done directly within the .onDelete method, using the ternary operator:
.onDelete(perform: check ? deleteItem : nil)

How to delete multiple rows from List in SwiftUI?

I took an example from this question: How does one enable selections in SwiftUI's List and edited the code to be able to delete rows one by one. But I don't know how to delete multiple rows from list.
Could you help me, please?
var demoData = ["Phil Swanson", "Karen Gibbons", "Grant Kilman", "Wanda Green"]
struct ContentView : View {
#State var selectKeeper = Set<String>()
var body: some View {
NavigationView {
List(selection: $selectKeeper){
ForEach(demoData, id: \.self) { name in
Text(name)
}
.onDelete(perform: delete)
}
.navigationBarItems(trailing: EditButton())
.navigationBarTitle(Text("Selection Demo \(selectKeeper.count)"))
}
}
func delete(at offsets: IndexSet) {
demoData.remove(atOffsets: offsets)
}
}
solution from SwiftUI how to perform action when EditMode changes?
struct Item: Identifiable {
let id = UUID()
let title: String
static var i = 0
init() {
self.title = "\(Item.i)"
Item.i += 1
}
}
struct ContentView: View {
#State var editMode: EditMode = .inactive
#State var selection = Set<UUID>()
#State var items = [Item(), Item(), Item()]
var body: some View {
NavigationView {
List(selection: $selection) {
ForEach(items) { item in
Text(item.title)
}
}
.navigationBarTitle(Text("Demo"))
.navigationBarItems(
leading: editButton,
trailing: addDelButton
)
.environment(\.editMode, self.$editMode)
}
}
private var editButton: some View {
Button(action: {
self.editMode.toggle()
self.selection = Set<UUID>()
}) {
Text(self.editMode.title)
}
}
private var addDelButton: some View {
if editMode == .inactive {
return Button(action: addItem) {
Image(systemName: "plus")
}
} else {
return Button(action: deleteItems) {
Image(systemName: "trash")
}
}
}
private func addItem() {
items.append(Item())
}
private func deleteItems() {
for id in selection {
if let index = items.lastIndex(where: { $0.id == id }) {
items.remove(at: index)
}
}
selection = Set<UUID>()
}
}
extension EditMode {
var title: String {
self == .active ? "Done" : "Edit"
}
mutating func toggle() {
self = self == .active ? .inactive : .active
}
}

Refreshing a SwiftUI List

Ím trying to refresh this List whenever I click on a NavLink
NavigationView {
List(feed.items.indices, id:\.self) { i in
NavigationLink(destination: ListFeedItemDetail(idx: i).environmentObject(self.feed)) {
ListFeedItem(item: self.$feed.items[i])
}
}
}
The list is made out of an array inside an environment object.
The problem: It does only refresh when I switch to another tab or close the app
I had used a modal View before and it worked there. (I did it with .onAppear)
Any Ideas?
Example
Problem: When you tap on an item in the list and tap the toggle button the EnvironmentObject is changed but this changed is only reflected when I change the tab and change it back again
import SwiftUI
import Combine
struct TestView: View {
#State var showSheet: Bool = false
#EnvironmentObject var feed: TestObject
func addObjects() {
var strings = ["one","two","three","four","five","six"]
for s in strings {
var testItem = TestItem(text: s)
self.feed.items.append(testItem)
}
}
var body: some View {
TabView {
NavigationView {
List(feed.items.indices, id:\.self) { i in
NavigationLink(destination: detailView(feed: self._feed, i: i)) {
HStack {
Text(self.feed.items[i].text)
Text("(\(self.feed.items[i].read.description))")
}
}
}
}
.tabItem({ Text("Test") })
.tag(0)
Text("Blank")
.tabItem({ Text("Test") })
.tag(0)
}.onAppear {
self.addObjects()
}
}
}
struct detailView: View {
#EnvironmentObject var feed: TestObject
var i: Int
var body: some View {
VStack {
Text(feed.items[i].text)
Text(feed.items[i].read.description)
Button(action: { self.feed.items[self.i].isRead.toggle() }) {
Text("Toggle read")
}
}
}
}
final class TestItem: ObservableObject {
init(text: String) {
self.text = text
self.isRead = false
}
static func == (lhs: TestItem, rhs: TestItem) -> Bool {
lhs.text < rhs.text
}
var text: String
var isRead: Bool
let willChange = PassthroughSubject<TestItem, Never>()
var read: Bool {
set {
self.isRead = newValue
}
get {
self.isRead
}
}
}
class TestObject: ObservableObject {
var willChange = PassthroughSubject<TestObject, Never>()
#Published var items: [TestItem] = [] {
didSet {
willChange.send(self)
}
}
}
I had a similar problem, this is the hack I came up with.
In your "TestView" declare:
#State var needRefresh: Bool = false
Pass this to your "detailView" destination, such as:
NavigationLink(destination: detailView(feed: self._feed, i: i, needRefresh: self.$needRefresh)) {
HStack {
Text(self.feed.items[i].text)
Text("(\(self.feed.items[i].read.description))")
}.accentColor(self.needRefresh ? .white : .black)
}
Note ".accentColor(self.needRefresh ? .white : .black)" to force a refresh when "needRefresh"
is changed.
In your "detailView" destination add:
#Binding var needRefresh: Bool
Then in your "detailView" in your Button action, add:
self.needRefresh.toggle()