Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 470 Vote(s) - 3.5 Average
  • 1
  • 2
  • 3
  • 4
  • 5
SwiftUI - Custom Swipe Actions In List

#1
How can I use custom Swipe Actions in SwiftUI?

I tried to use the UIKit Framework to get these working in SwiftUI. But that doesn't work for me.

```
import SwiftUI
import UIKit



init() {
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let important = importantAction(at: indexPath)
return UISwipeActionsConfiguration(actions: [important])
}
func importantAction(at indexPath: IndexPath) -> UIContextualAction {
let action = UIContextualAction(style: .normal, title: "Important") { (action, view, completion) in
print("HI")
}
action.backgroundColor = UIColor(hue: 0.0861, saturation: 0.76, brightness: 0.94, alpha: 1.0) /* #f19938 */
action.image = UIImage(named: "pencil")
return action
}
}






struct TestView: View {

NavigationView {
List {
ForEach(appointmentsViewModel.appointments.identified(by: \.id)) { appointment in Row_Appointments(appointment: appointment)
}.onDelete(perform: delete)
}
}
}
}
Reply

#2
## iOS 15+

In iOS 15 we can finally use native [Swipe Actions](

[To see links please register here]

):

```
func swipeActions<T>(edge: HorizontalEdge = .trailing, allowsFullSwipe: Bool = true, content: () -> T) -> some View where T : View
```
They can be attached to the `ForEach` container just like `onMove` or `onDelete`:
```
List {
ForEach(appointmentsViewModel.appointments.identified(by: \.id)) { appointment in
Row_Appointments(appointment: appointment)
}
.swipeActions(edge: .trailing) {
Button {
print("Hi")
} label: {
Label("Important", systemImage: "pencil")
}
}
}
```
Reply

#3
**If your deployment target is iOS 15 (or newer)**, then you can use the [`swipeActions` modifier](

[To see links please register here]

) to customize the swipe actions of a list item.

This also applies to watchOS 8 and macOS 12.

These operating systems will be released in late 2021.

Prior to the late 2021 version of SwiftUI, there is no support for custom swipe actions for `List` items.

If you need to target an older version, you would probably be better off implementing a different user interface, like adding a toggle button as a subview of your list item, or [adding a context menu](

[To see links please register here]

) to your list item.
Reply

#4
Delighted to see that iOS 15 brings the long awaited `.swipeActions` view modifier to `List` in SwiftUI with an easy to use API.

```
List {
ForEach(store.messages) { message in
MessageCell(message: message)
.swipeActions(edge: .leading) {
Button { store.toggleUnread(message) } label: {
if message.isUnread {
Label("Read", systemImage: "envelope.open")
} else {
Label("Unread", systemImage: "envelope.badge")
}
}
}
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
store.delete(message)
} label: {
Label("Delete", systemImage: "trash")
}
Button { store.flag(message) } label: {
Label("Flag", systemImage: "flag")
}
}
}
}
}
```

Actions appear in the order listed, starting from the originating edge working inwards.

The example above produces:

![swipe actions](

[To see links please register here]

)


Note that `swipeActions` override the `onDelete` handler if provided that is available on `ForEach`

[Read more in Apple's developer docs](

[To see links please register here]

)
Reply

#5
Now with IOS 15 , Swift 5.5 we can add a Swipe action like this





struct ContentView: View {
@State private var total = 0

var body: some View {
NavigationView {
List {
ForEach(1..<100) { i in
Text("\(i)")
.swipeActions(edge: .leading) {
Button {
total += i
} label: {
Label("Add \(i)", systemImage: "plus.circle")
}
.tint(.indigo)
}
.swipeActions(edge: .trailing) {
Button {
total -= i
} label: {
Label("Subtract \(i)", systemImage: "minus.circle")
}
}
}
}
.navigationTitle("Total: \(total)")
}
}
}
Reply

#6


I wanted the same and have now the following implementation.

The SwipeController checks when to execute a swipe action and performs the SwipeAction, for now you can add your swipe actions under the print lines in the executeAction function. But it is better make an abstract class from this.

Then in the SwipeLeftRightContainer struct we have most of the logic in the DragGesture. What it does is while your dragging its gonna change the offset and then make calls to the SwipeController to see if the threshold for swipe left or right are reached. Then when you finish the dragging it will come into the onEnded callback of the DragGesture. Here we will reset the offset and let the SwipeController decide to execute an action.

Keep in mind lot of the variables in the view are static for an iPhone X so you should change them to what fits best.

Also what this does is creating an action for left and right swipe, but you can adjust it to your own use ofcourse.

import SwiftUI

/** executeRight: checks if it should execute the swipeRight action
execute Left: checks if it should execute the swipeLeft action
submitThreshold: the threshold of the x offset when it should start executing the action
*/
class SwipeController {
var executeRight = false
var executeLeft = false
let submitThreshold: CGFloat = 200

func checkExecutionRight(offsetX: CGFloat) {
if offsetX > submitThreshold && self.executeRight == false {
Utils.HapticSuccess()
self.executeRight = true
} else if offsetX < submitThreshold {
self.executeRight = false
}
}

func checkExecutionLeft(offsetX: CGFloat) {
if offsetX < -submitThreshold && self.executeLeft == false {
Utils.HapticSuccess()
self.executeLeft = true
} else if offsetX > -submitThreshold {
self.executeLeft = false
}
}

func excuteAction() {
if executeRight {
print("executed right")
} else if executeLeft {
print("executed left")
}

self.executeLeft = false
self.executeRight = false
}
}

struct SwipeLeftRightContainer: View {

var swipeController: SwipeController = SwipeController()

@State var offsetX: CGFloat = 0

let maxWidth: CGFloat = 335
let maxHeight: CGFloat = 125
let swipeObjectsOffset: CGFloat = 350
let swipeObjectsWidth: CGFloat = 400

@State var rowAnimationOpacity: Double = 0
var body: some View {
ZStack {
Group {
HStack {
Text("Sample row")
Spacer()
}
}.padding(10)
.zIndex(1.0)
.frame(width: maxWidth, height: maxHeight)
.cornerRadius(5)
.background(RoundedRectangle(cornerRadius: 10).fill(Color.gray))
.padding(10)
.offset(x: offsetX)
.gesture(DragGesture(minimumDistance: 5).onChanged { gesture in
withAnimation(Animation.linear(duration: 0.1)) {
offsetX = gesture.translation.width
}
swipeController.checkExecutionLeft(offsetX: offsetX)
swipeController.checkExecutionRight(offsetX: offsetX)
}.onEnded { _ in
withAnimation(Animation.linear(duration: 0.1)) {
offsetX = 0
swipeController.prevLocX = 0
swipeController.prevLocXDiff = 0
self.swipeController.excuteAction()
}
})
Group {
ZStack {
Rectangle().fill(Color.red).frame(width: swipeObjectsWidth, height: maxHeight).opacity(opacityDelete)
Image(systemName: "multiply").font(Font.system(size: 34)).foregroundColor(Color.white).padding(.trailing, 150)
}
}.zIndex(0.9).offset(x: swipeObjectsOffset + offsetX)
Group {
ZStack {
Rectangle().fill(Color.green).frame(width: swipeObjectsWidth, height: maxHeight).opacity(opacityLike)
Image(systemName: "heart").font(Font.system(size: 34)).foregroundColor(Color.white).padding(.leading, 150)
}
}.zIndex(0.9).offset(x: -swipeObjectsOffset + offsetX)
}
}

var opacityDelete: Double {
if offsetX < 0 {
return Double(abs(offsetX) / 50)
}
return 0
}

var opacityLike: Double {
if offsetX > 0 {
return Double(offsetX / 50)
}
return 0
}
}

struct SwipeListView: View {

var body: some View {
ScrollView {
ForEach(0..<10) { index in
SwipeLeftRightContainer().listRowInsets(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10))
}
}
}

}

struct SwipeLeftRight_Previews: PreviewProvider {
static var previews: some View {
SwipeListView()
}
}

Reply

#7
Based on [Michał Ziobro answer][1] using [Introspect][2] to simplify table view delegate setup.

Note that this will override the table view delegate and might **BREAK** some of the existing table view behaviours. While things such as header hight can be fixed by adding the method to custom delegate yourself, other might not be fixable.

```swift
struct ListSwipeActions: ViewModifier {

@ObservedObject var coordinator = Coordinator()

func body(content: Content) -> some View {

return content
.introspectTableView { tableView in
tableView.delegate = self.coordinator
}
}

class Coordinator: NSObject, ObservableObject, UITableViewDelegate {

func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}

func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

let archiveAction = UIContextualAction(style: .normal, title: "Title") { action, view, completionHandler in
// update data source
completionHandler(true)
}
archiveAction.image = UIImage(systemName: "archivebox")!
archiveAction.backgroundColor = .systemYellow

let configuration = UISwipeActionsConfiguration(actions: [archiveAction])

return configuration
}
}
}

extension List {
func swipeActions() -> some View {
return self.modifier(ListSwipeActions())
}
}
```


[1]:

[To see links please register here]

[2]:

[To see links please register here]

Reply

#8
It is able to be done in the way something like this:

List {
ForEach(items) { (item) in

Text("\(item.title)")
}
.onDelete(perform: self.delete)
}.swipeActions()


Then you need to add this swipeActions() modifier

struct ListSwipeActions: ViewModifier {

@ObservedObject var coordinator = Coordinator()

func body(content: Content) -> some View {

return content
.background(TableViewConfigurator(configure: { tableView in
delay {
tableView.delegate = self.coordinator
}
}))
}

class Coordinator: NSObject, ObservableObject, UITableViewDelegate {

func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("Scrolling ....!!!")
}

func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}

func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

let isArchived = false
let title = isArchived ? NSLocalizedString("Unarchive", comment: "Unarchive") : NSLocalizedString("Archive", comment: "Archive")

let archiveAction = UIContextualAction(style: .normal, title: title, handler: {
(action, view, completionHandler) in

// update data source
completionHandler(true)
})
archiveAction.title = title
archiveAction.image = UIImage(systemName: "archivebox")!
archiveAction.backgroundColor = .systemYellow

let configuration = UISwipeActionsConfiguration(actions: [archiveAction])

return configuration
}
}
}

extension List {

func swipeActions() -> some View {
return self.modifier(ListSwipeActions())
}
}

And have `TableViewConfigurator` that searches for table view behind the `List`

struct TableViewConfigurator: UIViewControllerRepresentable {

var configure: (UITableView) -> Void = { _ in }

func makeUIViewController(context: Context) -> UIViewController {

UIViewController()
}

func updateUIViewController(_ uiViewController: UIViewController, context: Context) {


let tableViews = UIApplication.nonModalTopViewController()?.navigationController?.topViewController?.view.subviews(ofType: UITableView.self) ?? [UITableView]()

for tableView in tableViews {
self.configure(tableView)
}
}
}
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through