Created
July 6, 2020 09:56
-
-
Save agelessman/3bf1d4f4d7fb24495972ba962777874f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ContentView.swift | |
// ScrollViewDemo | |
// | |
// Created by MC on 2020/7/5. | |
// | |
import SwiftUI | |
struct ContentView: View { | |
var body: some View { | |
Example3() | |
} | |
} | |
struct Example4: View { | |
@State private var offsetY: CGFloat = 0 | |
var body: some View { | |
VStack { | |
Spacer() | |
Text("offseetX: \(offsetY)") | |
Spacer() | |
HStack { | |
Button("offseetY: 0") { | |
self.offsetY = 0 | |
} | |
.border(Color.green) | |
Button("offsetY: 100") { | |
self.offsetY = 100 | |
} | |
.border(Color.green) | |
Button("offsetY: 200") { | |
self.offsetY = 200 | |
} | |
.border(Color.green) | |
} | |
Spacer() | |
ScrollView(.vertical) { | |
LazyVStack(spacing: 20) { | |
ForEach(0..<10) { index in | |
Text("\(index)") | |
.frame(width: 200, height: 140) | |
.background(index == 6 ? Color.green : Color.orange.opacity(0.5)) | |
.cornerRadius(/*@START_MENU_TOKEN@*/3.0/*@END_MENU_TOKEN@*/) | |
} | |
} | |
} | |
.scrollOffsetY(self.$offsetY) | |
Spacer() | |
} | |
} | |
} | |
struct Example3: View { | |
@State private var offsetX: CGFloat = 0 | |
var body: some View { | |
VStack { | |
Spacer() | |
Text("offseetX: \(offsetX)") | |
Spacer() | |
HStack { | |
Button("offseetX: 0") { | |
self.offsetX = 0 | |
} | |
.border(Color.green) | |
Button("offseetX: 100") { | |
self.offsetX = 100 | |
} | |
.border(Color.green) | |
Button("offseetX: 200") { | |
self.offsetX = 200 | |
} | |
.border(Color.green) | |
} | |
Spacer() | |
ScrollView(.horizontal) { | |
LazyHStack { | |
ForEach(0..<10) { index in | |
Text("\(index)") | |
.frame(width: 100, height: 240) | |
.background(index == 6 ? Color.green : Color.orange.opacity(0.5)) | |
.cornerRadius(/*@START_MENU_TOKEN@*/3.0/*@END_MENU_TOKEN@*/) | |
} | |
} | |
} | |
.scrollOffsetX(self.$offsetX) | |
.frame(height: 300) | |
Spacer() | |
} | |
} | |
} | |
extension View { | |
func scrollOffsetX(_ offsetX: Binding<CGFloat>) -> some View { | |
return MyScrollViewControllerRepresentable(offset: offsetX, isOffsetX: true, content: self) | |
} | |
} | |
extension View { | |
func scrollOffsetY(_ offsetY: Binding<CGFloat>) -> some View { | |
return MyScrollViewControllerRepresentable(offset: offsetY, isOffsetX: false, content: self) | |
} | |
} | |
struct MyScrollViewControllerRepresentable<Content>: UIViewControllerRepresentable where Content: View { | |
var offset: Binding<CGFloat> | |
let isOffsetX: Bool | |
var content: Content | |
func makeUIViewController(context: Context) -> MyScrollViewUIHostingController<Content> { | |
MyScrollViewUIHostingController(offset: offset, isOffsetX:isOffsetX, rootView: content) | |
} | |
func updateUIViewController(_ uiViewController: MyScrollViewUIHostingController<Content>, context: Context) { | |
uiViewController.scroll(to: offset.wrappedValue, animated: true) | |
} | |
} | |
class MyScrollViewUIHostingController<Content>: UIHostingController<Content> where Content: View { | |
var offset: Binding<CGFloat> | |
let isOffsetX: Bool | |
var showed = false | |
var sv: UIScrollView? | |
init(offset: Binding<CGFloat>, isOffsetX: Bool, rootView: Content) { | |
self.offset = offset | |
self.isOffsetX = isOffsetX | |
super.init(rootView: rootView) | |
} | |
@objc dynamic required init?(coder aDecoder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
override func viewDidAppear(_ animated: Bool) { | |
/// 保证设置一次监听 | |
if showed { | |
return | |
} | |
showed = true | |
/// 查找UIScrollView | |
sv = findScrollView(in: view) | |
/// 设置监听 | |
sv?.addObserver(self, | |
forKeyPath: #keyPath(UIScrollView.contentOffset), | |
options: [.old, .new], | |
context: nil) | |
/// 滚动到指定位置 | |
scroll(to: offset.wrappedValue, animated: false) | |
super.viewDidAppear(animated) | |
} | |
func scroll(to position: CGFloat, animated: Bool = true) { | |
if let s = sv { | |
if position != (self.isOffsetX ? s.contentOffset.x : s.contentOffset.y) { | |
let offset = self.isOffsetX ? CGPoint(x: position, y: 0) : CGPoint(x: 0, y: position) | |
sv?.setContentOffset(offset, animated: animated) | |
} | |
} | |
} | |
override func observeValue(forKeyPath keyPath: String?, | |
of object: Any?, | |
change: [NSKeyValueChangeKey: Any]?, | |
context: UnsafeMutableRawPointer?) { | |
if keyPath == #keyPath(UIScrollView.contentOffset) { | |
if let s = self.sv { | |
DispatchQueue.main.async { | |
self.offset.wrappedValue = self.isOffsetX ? s.contentOffset.x : s.contentOffset.y | |
} | |
} | |
} | |
} | |
func findScrollView(in view: UIView?) -> UIScrollView? { | |
if view?.isKind(of: UIScrollView.self) ?? false { | |
return view as? UIScrollView | |
} | |
for subview in view?.subviews ?? [] { | |
if let sv = findScrollView(in: subview) { | |
return sv | |
} | |
} | |
return nil | |
} | |
} | |
struct Example2: View { | |
var body: some View { | |
ScrollViewRepresentable() | |
.frame(width: 200, height: 100) | |
} | |
struct ScrollViewRepresentable: UIViewRepresentable { | |
func makeUIView(context: Context) -> UIScrollView { | |
let scrollView = UIScrollView() | |
scrollView.backgroundColor = UIColor.green | |
return scrollView | |
} | |
func updateUIView(_ uiView: UIScrollView, context: Context) {} | |
} | |
} | |
struct Example1: View { | |
var body: some View { | |
ScrollView(.horizontal) { | |
ScrollViewReader { scrollViewProxy in | |
LazyHStack(spacing: 10) { | |
VStack { | |
Spacer() | |
Text(".leading") | |
.frame(width: 80, height: 40, alignment: /*@START_MENU_TOKEN@*/ .center/*@END_MENU_TOKEN@*/) | |
.foregroundColor(.white) | |
.background(Color.green) | |
.onTapGesture { | |
withAnimation { | |
scrollViewProxy.scrollTo(6, anchor: .leading) | |
} | |
} | |
Spacer() | |
Text(".center") | |
.frame(width: 80, height: 40, alignment: .center) | |
.foregroundColor(.white) | |
.background(Color.green) | |
.onTapGesture { | |
withAnimation { | |
scrollViewProxy.scrollTo(6, anchor: .center) | |
} | |
} | |
Spacer() | |
Text(".trailing") | |
.frame(width: 80, height: 40, alignment: /*@START_MENU_TOKEN@*/ .center/*@END_MENU_TOKEN@*/) | |
.foregroundColor(.white) | |
.background(Color.green) | |
.onTapGesture { | |
withAnimation { | |
scrollViewProxy.scrollTo(6, anchor: .trailing) | |
} | |
} | |
Spacer() | |
} | |
.frame(width: 100, height: 240) | |
ForEach(0..<10) { index in | |
Text("\(index)") | |
.frame(width: 100, height: 240) | |
.background(index == 6 ? Color.green : Color.orange.opacity(0.5)) | |
.cornerRadius(/*@START_MENU_TOKEN@*/3.0/*@END_MENU_TOKEN@*/) | |
} | |
} | |
.padding(.horizontal, /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/) | |
} | |
} | |
.frame(height: 300, alignment: /*@START_MENU_TOKEN@*/ .center/*@END_MENU_TOKEN@*/) | |
} | |
} | |
struct ContentView_Previews: PreviewProvider { | |
static var previews: some View { | |
ContentView() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment