diff --git a/Demo/Demo/ContentView.swift b/Demo/Demo/ContentView.swift index da46ffc..c314ab5 100644 --- a/Demo/Demo/ContentView.swift +++ b/Demo/Demo/ContentView.swift @@ -51,22 +51,21 @@ struct ContentView: View { List{ demo1() .swipeCell(cellPosition: .right, leftSlot: nil, rightSlot: slot1) - + demo2() .swipeCell(cellPosition: .both, leftSlot: slot1, rightSlot: slot1) - + demo3() .swipeCell(cellPosition: .right, leftSlot: nil, rightSlot: slot3) - + demo4() .swipeCell(cellPosition: .left, leftSlot: slot2, rightSlot: nil) - + demo5() .swipeCell(cellPosition: .left, leftSlot: slot4, rightSlot: nil) - + demo6() .swipeCell(cellPosition: .both, leftSlot: slot1, rightSlot: slot1 ,swipeCellStyle: SwipeCellStyle(alignment: .leading, dismissWidth: 20, appearWidth: 20, destructiveWidth: 240, vibrationForButton: .error, vibrationForDestructive: .heavy, autoResetTime: 3)) - demo7() .swipeCell(cellPosition: .right, leftSlot: nil, rightSlot: slot5) .alert(isPresented: $showAlert){ @@ -75,16 +74,12 @@ struct ContentView: View { dismissDestructiveDelayButton() }),secondaryButton: .cancel({dismissDestructiveDelayButton()})) } - - ForEach(0..<30){ i in - Text("Scroll List can dismiss button") - .frame(height:100,alignment: .center) - .swipeCell(cellPosition: .both, leftSlot: slot1, rightSlot: slot1) - } + NavigationLink("ScrollView LazyVStack",destination:demo9()) + NavigationLink("ScrollView single Cell",destination:Demo8()) } .navigationBarTitle("SwipeCell Demo",displayMode: .inline) } - .dismissSwipeCellFast() + .dismissSwipeCell() .sheet(isPresented: $showSheet, content: {Text("Hello world")}) } @@ -92,7 +87,7 @@ struct ContentView: View { func demo1() -> some View{ HStack{ Spacer() - Text("Swipe left") + Text("← Swipe left") if bookmark { Image(systemName: "bookmark.fill") .font(.largeTitle) @@ -111,7 +106,7 @@ struct ContentView: View { func demo2() -> some View{ HStack{ Spacer() - Text("Sliding on both sides") + Text("← → Sliding on both sides") if bookmark { Image(systemName: "bookmark.fill") .font(.largeTitle) @@ -131,7 +126,7 @@ struct ContentView: View { HStack{ Spacer() VStack{ - Text("Swipe left") + Text("⇠ Swipe left") Text("MutliButton with destructive button") } Spacer() @@ -143,7 +138,7 @@ struct ContentView: View { HStack{ Spacer() VStack{ - Text("Swipe right") + Text("⇢ Swipe right") Text("One destructive button") } Spacer() @@ -155,7 +150,7 @@ struct ContentView: View { HStack{ Spacer() VStack{ - Text("Swipe right") + Text("→ Swipe right") Text("Dynamic Button") } Spacer() @@ -167,8 +162,8 @@ struct ContentView: View { HStack{ Spacer() VStack{ - Text("You can set the auto reset duration ") - Text("3 sec") + Text("← You can set the auto reset duration ") + Text("please wait 3 sec") } Spacer() } @@ -179,12 +174,32 @@ struct ContentView: View { HStack{ Spacer() VStack{ - Text("destructiveDelay Button") + Text("← destructiveDelay Button") + Text("click delete") } Spacer() } .frame(height:100) } + + func demo9() -> some View{ + let button4 = SwipeCellButton(buttonStyle: .titleAndImage, title: "New", systemImage: "bubble.left.and.bubble.right.fill", titleColor: .white, imageColor: .white, view: nil, backgroundColor: .blue, action: {}, feedback: true) + + let button5 = SwipeCellButton(buttonStyle: .titleAndImage, title: "Delete", systemImage: "trash", titleColor: .white, imageColor: .white, view: nil, backgroundColor: .red, action: {}, feedback: true) + let slot = SwipeCellSlot(slots: [button4,button5]) + let lists = (0...100).map{$0} + return ScrollView{ + LazyVStack{ + ForEach(lists,id:\.self){ item in + Text("Swipe in scrollView:\(item)") + .frame(height:80) + .swipeCell(cellPosition: .both, leftSlot:slot, rightSlot: slot) + .dismissSwipeCellForScrollViewForLazyVStack() + } + } + } + } + } struct ContentView_Previews: PreviewProvider { @@ -193,3 +208,68 @@ struct ContentView_Previews: PreviewProvider { } } +struct Demo8:View{ + let button1 = SwipeCellButton(buttonStyle: .view, title: "", systemImage: "", view: {AnyView( + Circle() + .fill(Color.blue) + .frame(width:40,height:40) + .overlay( + Image(systemName: "arrowshape.turn.up.left.fill") + .font(.headline) + .foregroundColor(.white) + ) + )}, backgroundColor: .clear, action: {}) + + let button2 = SwipeCellButton(buttonStyle: .view, title: "", systemImage: "", view: {AnyView( + Circle() + .fill(Color.orange) + .frame(width:40,height:40) + .overlay( + Image(systemName: "flag.fill") + .font(.headline) + .foregroundColor(.white) + ) + )}, backgroundColor: .clear, action: {}) + + let button3 = SwipeCellButton(buttonStyle: .view, title: "", systemImage: "", view: {AnyView( + Circle() + .fill(Color.red) + .frame(width:40,height:40) + .overlay( + Image(systemName: "trash.fill") + .font(.headline) + .foregroundColor(.white) + ) + )}, backgroundColor: .clear, action: {}) + + let button4 = SwipeCellButton(buttonStyle: .view, title: "", systemImage: "", view: {AnyView( + Circle() + .fill(Color.blue) + .frame(width:40,height:40) + .overlay( + Image(systemName: "envelope.badge.fill") + .font(.headline) + .foregroundColor(.white) + ) + )}, backgroundColor: .clear, action: {}) + + var body: some View{ + let rightSlot = SwipeCellSlot(slots: [button1,button2,button3],buttonWidth: 50) + let leftSlot = SwipeCellSlot(slots: [button4],buttonWidth: 50) + ScrollView{ + VStack{ + Text("SwipeCell in ScrollView") + .dismissSwipeCellForScrollView() //目前在ScrollView下注入的方式在iOS14下有点问题,所以必须将dissmissSwipeCellForScrollView放置在ScrollView内部 + //dismissSwipeCellForScrollView 只能用于 VStack, 如果是LazyVStack请使用dismissSwipeCellForScrollViewForLazyVStack + ForEach(0..<40){ _ in + Text("mail content....") + } + Text("End") + } + .frame(maxWidth:.infinity,maxHeight: .infinity) + } + .swipeCell(cellPosition: .both, leftSlot: leftSlot, rightSlot: rightSlot,clip: false) + } +} + + diff --git a/README.md b/README.md index 8bff321..b8f56e4 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ cellView() ``` ## 滚动列表自动消除 +*For List* ```swift List{ ``` @@ -76,9 +77,41 @@ cellView() .dismissSwipeCell() } ``` -* dismissSwipeCell 在editmode下支持选择,但响应较慢 -* dismissSwipeCellFast 在editmode下选择cell有问题,但响应迅速 -* dismissSwipeCellForScrollView 用于ScrollView + +*For single cell in ScrollView* +```swift +ScrollView{ + VStack{ + Text("Mail Title") + .dismissSwipeCellForScrollView() + Text("Mail Content") + .... + } + .frame(maxWidth:.infinity,maxHeight: .infinity) +} +.swipeCell(cellPosition: .both, leftSlot: leftSlot, rightSlot: rightSlot,clip: false) +``` + +*For LazyVStack in ScrollView* +```swift +ScrollView{ + LazyVStack{ + ForEach(lists,id:\.self){ item in + Text("Swipe in scrollView:\(item)") + .frame(height:80) + .swipeCell(cellPosition: .both, leftSlot:slot, rightSlot: slot) + .dismissSwipeCellForScrollViewForLazyVStack() + } + } +} +``` + + + +* dismissSwipeCell 在editmode下支持选择 +* dismissSwipeCellForScrollView 用于ScrollView,通常用于只有一个Cell的场景,比如说Mail中的邮件内容显示.参看Demo中的演示 +* dismissSwipeCellForScrollViewForLazyVStack 用于ScrollView中使用LazyVStack场景. + 由于SwiftUI没有很好的方案能够获取滚动状态,所以采用了 [Introspect](https://github.com/siteline/SwiftUI-Introspect.git)实现的上述功能. diff --git a/Sources/SwipeCell/ScrollNotification.swift b/Sources/SwipeCell/ScrollNotification.swift index 6a0715d..5cdc7dd 100644 --- a/Sources/SwipeCell/ScrollNotification.swift +++ b/Sources/SwipeCell/ScrollNotification.swift @@ -6,7 +6,13 @@ import SwiftUI import Introspect import Combine -//这个版本的dismiss响应及时,不过会产生和SwiftUI List的一些冲突,导致删除和选择会有问题.所以屏蔽的删除.如果你不需要选择并自己实现删除,这个版本会给你最快速的滚动后SwipeButton复位动作 +/* + 这个版本的dismiss响应及时,不过会产生和SwiftUI List的一些冲突, + 导致删除和选择会有问题.所以屏蔽的删除.如果你不需要选择并自己实现删除,这个版本会给你最快速的滚动后SwipeButton复位动作 + 另外,这个dismissSwipeCellFast不支持Button响应,包括NavitionLink.如果你确定要使用,请使用onTapGesture来响应点击. +总之,如果如果你不很清楚,那么就使用dismissSwipeCell +*/ +//MARK: dismissList1 not suggest now extension View{ public func dismissSwipeCellFast() -> some View{ self @@ -41,10 +47,11 @@ class Delegate:NSObject,UITableViewDelegate, UIScrollViewDelegate,ObservableObje } } - +//MARK: dismissList //这个版本对于SwiftUI的List支持更好一点(可以支持选择),.不过响应稍有延迟.另外,屏幕上的Cell必须要滚动至少一个才能开始dismiss //如果在ForEach上使用了onDelete,系统会自动在Cell右侧添加删除按钮替代自定义的swipeButton. struct ScrollNotificationWithoutInject:ViewModifier{ + let timeInterval:Double @State var timer = Timer.publish(every: 0.5, on: .main, in: .common) @State var cancellable:Set = [] @State var listView = UITableView() @@ -57,7 +64,7 @@ struct ScrollNotificationWithoutInject:ViewModifier{ self.listView = listView } .onAppear{ - timer = Timer.publish(every: 1, on: .main, in: .common) + timer = Timer.publish(every: timeInterval, on: .main, in: .common) timer.connect() .store(in: &cancellable) } @@ -77,15 +84,15 @@ struct ScrollNotificationWithoutInject:ViewModifier{ } extension View{ - public func dismissSwipeCell() -> some View{ + public func dismissSwipeCell(timeInterval:Double = 0.5) -> some View{ self - .modifier(ScrollNotificationWithoutInject()) + .modifier(ScrollNotificationWithoutInject(timeInterval: timeInterval)) } } //ScrollView使用的dismiss.当前在ios13下使用没有问题,不过Introspect在iOS14的beta下无法获取数据.相信过段时间便能修复. -struct ScrollNotificationForScrollView:ViewModifier{ +struct ScrollNotificationForScrollViewInject:ViewModifier{ @State var timer = Timer.publish(every: 0.5, on: .main, in: .common) @State var cancellable:Set = [] @State var scrollView = UIScrollView() @@ -116,12 +123,102 @@ struct ScrollNotificationForScrollView:ViewModifier{ } extension View{ - public func dismissSwipeCellForScrollView() -> some View{ + public func dismissSwipeCellForScrollViewInject() -> some View{ self - .modifier(ScrollNotificationForScrollView()) + .modifier(ScrollNotificationForScrollViewInject()) } } public func dismissDestructiveDelayButton(){ NotificationCenter.default.post(name: .swipeCellReset, object: nil) } + +//MARK: DismissScrollView for VStack +struct TopLeadingY:Equatable{ + let topLeadingY:CGFloat +} + +struct ScrollViewPreferencKey:PreferenceKey{ + typealias Value = [TopLeadingY] + static var defaultValue: Value = [] + static func reduce(value: inout Value, nextValue: () -> Value) { + value = nextValue() + } +} + +struct DismissSwipeCellForScrollView:ViewModifier{ + @State var topleadingY:CGFloat? = nil + func body(content: Content) -> some View { + GeometryReader{ proxy in + ZStack{ + Color.clear + content + .preference(key: ScrollViewPreferencKey.self,value:[TopLeadingY(topLeadingY:proxy.frame(in: .global).minY)]) + } + } + .onPreferenceChange(ScrollViewPreferencKey.self){ preference in + if topleadingY != preference.first!.topLeadingY { + NotificationCenter.default.post(name: .swipeCellReset, object: nil) + } + else { + topleadingY = preference.first!.topLeadingY + } + } + } +} + +extension View{ + public func dismissSwipeCellForScrollView() -> some View{ + self + .modifier(DismissSwipeCellForScrollView()) + } +} + +//MARK: DismissScrollView for LazyVStack +//LazyVStack的实现目前没有太好的方案.个别情况下会打断滑动按钮的出现动画 +struct CellInfo:Equatable{ + let id:UUID +} + +struct ScrollViewPreferencKeyForLazy:PreferenceKey{ + typealias Value = [CellInfo] + static var defaultValue: Value = [] + static func reduce(value: inout Value, nextValue: () -> Value) { + value.append(contentsOf: nextValue()) + } +} + +struct DismissSwipeCellForScrollViewForLazy:ViewModifier{ + @State var cellinfos:[CellInfo] = [] + func body(content: Content) -> some View { + content + .background( + GeometryReader{proxy in + Color.clear + .preference(key: ScrollViewPreferencKeyForLazy.self,value:[CellInfo(id: UUID())]) + } + ) + .onPreferenceChange(ScrollViewPreferencKeyForLazy.self){ preference in + if cellinfos.count == 0 { + DispatchQueue.main.async { + cellinfos = preference + } + } + if cellinfos != preference { + NotificationCenter.default.post(name: .swipeCellReset, object: nil) + } + else { + DispatchQueue.main.async { + cellinfos = preference + } + } + } + } +} + +extension View{ + public func dismissSwipeCellForScrollViewForLazyVStack() -> some View{ + self + .modifier(DismissSwipeCellForScrollViewForLazy()) + } +} diff --git a/Sources/SwipeCell/SwipeCellViewModifier1.swift b/Sources/SwipeCell/SwipeCellViewModifier1.swift index e6d0d48..3e404a5 100644 --- a/Sources/SwipeCell/SwipeCellViewModifier1.swift +++ b/Sources/SwipeCell/SwipeCellViewModifier1.swift @@ -14,6 +14,7 @@ struct SwipeCellModifier:ViewModifier{ let leftSlot:SwipeCellSlot? let rightSlot:SwipeCellSlot? let swipeCellStyle:SwipeCellStyle + let clip:Bool @State var status:CellStatus = .showCell @State var showDalayButtonWith:CGFloat = 0 @@ -183,11 +184,11 @@ struct SwipeCellModifier:ViewModifier{ } } - func getNameId(i:Int,position:SwipeCellSlotPosition) -> Int { - cellID.hashValue + i + position.rawValue - } +// func getNameId(i:Int,position:SwipeCellSlotPosition) -> Int { +// cellID.hashValue + i + position.rawValue +// } - @State var count = 0 +// @State var count = 0 func loadButtons(_ slot:SwipeCellSlot,position:SwipeCellSlotPosition,frame:CGRect) -> some View{ let buttons = slot.slots diff --git a/Sources/SwipeCell/SwipeCellViewModifier2.swift b/Sources/SwipeCell/SwipeCellViewModifier2.swift index 1529392..059d708 100644 --- a/Sources/SwipeCell/SwipeCellViewModifier2.swift +++ b/Sources/SwipeCell/SwipeCellViewModifier2.swift @@ -8,12 +8,13 @@ import SwiftUI extension SwipeCellModifier{ - func body(content: Content) -> some View { if editMode?.wrappedValue == .active {dismissNotification()} + return ZStack(alignment:.topLeading){ + Color.clear.zIndex(0) ZStack{ - Color.clear.zIndex(0) + //加载左侧按钮 GeometryReader{ proxy in @@ -52,7 +53,9 @@ extension SwipeCellModifier{ } .contentShape(Rectangle()) .gesture(getGesture()) - .clipShape(Rectangle()) + .ifIs(clip){ + $0.clipShape(Rectangle()) + } .onReceive(resetNotice){ notice in //如果其他的cell发送通知或者list发送通知,则本cell复位 if cellID != notice.object as? UUID { diff --git a/Sources/SwipeCell/ViewExtension.swift b/Sources/SwipeCell/ViewExtension.swift index e8f676a..3b9090f 100644 --- a/Sources/SwipeCell/ViewExtension.swift +++ b/Sources/SwipeCell/ViewExtension.swift @@ -5,7 +5,7 @@ import SwiftUI extension View{ - public func swipeCell(cellPosition:SwipeCellSlotPosition,leftSlot:SwipeCellSlot?,rightSlot:SwipeCellSlot?,swipeCellStyle:SwipeCellStyle = .defaultStyle(),disable:Bool = false) -> some View{ + public func swipeCell(cellPosition:SwipeCellSlotPosition,leftSlot:SwipeCellSlot?,rightSlot:SwipeCellSlot?,swipeCellStyle:SwipeCellStyle = .defaultStyle(),clip:Bool = true,disable:Bool = false) -> some View{ var d = disable if cellPosition == .none { d = true @@ -16,7 +16,7 @@ extension View{ else { return AnyView( self - .modifier(SwipeCellModifier(cellPosition: cellPosition, leftSlot: leftSlot, rightSlot: rightSlot, swipeCellStyle: swipeCellStyle)) + .modifier(SwipeCellModifier(cellPosition: cellPosition, leftSlot: leftSlot, rightSlot: rightSlot, swipeCellStyle: swipeCellStyle,clip:clip)) ) } }