Open zntfdr opened 5 years ago
Thank you for your attempt for Swift UI! I'm sorry I haven't yet tried the lib with SwiftUI because I'm working recently for v2 which beta I'm going to release next month.(v2 will resolve many issues in GitHub). But I would like to try your example and take a look at scroll tracking problem later.
Hi @SCENEE, thank you very much π
@zntfdr Where you able to resolve this? I am facing the same issue here. @SCENEE is v2 still in pogress?
@sipersso Yes it is. But now Iβm focusing to fix some issue on v1.x and I would like to release v2 on March.
@sipersso I've tried multiple ways, such as embedding SwiftUI views into auto-resizing UITableViewCell
s, so far no result was acceptable.
If you really want to use SwiftUI today, my suggestion is to implement this floating panel yourself (I haven't done it): with a geometry reader and a gesture recogniser you should be able to accomplish everything you need.
Thank you @SCENEE for all your support β€οΈ
I agree with, it is a great library @SCENEE.
@zntfdr I did get scroll tracking to work with SwiftUI. The trick, which is missing from your example is to set the content size of the scrollView to the size of the hostingController view.
let width = UIScreen.main.bounds.width
let size = hostingController.view.sizeThatFits(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude))
hostingController.view.frame = CGRect(x: 0, y: 0, width: width, height: size.height)
scrollView.addSubview(hostingController.view)
scrollView.contentSize = CGSize(width: width, height: size.height)
The downside is that if you the swiftui view is resized you would have to reset the contentsize and I am not sure how to get notified to do that. Other than that it seems to work pretty great and it does allow for interaction in SwiftUI as well as track scrolling.
I also did try to recreate the Panel in SwiftUI, but none of my attempts where nearly as good as the component that @SCENEE has built. Using the hybrid approach, UIKit panel, but SwiftUI content, makes it really easy to use from a UIKit UIViewController ;)
@sipersso
@zntfdr I did get scroll tracking to work with SwiftUI. The trick, which is missing from your example is to set the content size of the scrollView to the size of the hostingController view.
let width = UIScreen.main.bounds.width let size = hostingController.view.sizeThatFits(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)) hostingController.view.frame = CGRect(x: 0, y: 0, width: width, height: size.height) scrollView.addSubview(hostingController.view) scrollView.contentSize = CGSize(width: width, height: size.height)
The downside is that if you the swiftui view is resized you would have to reset the contentsize and I am not sure how to get notified to do that. Other than that it seems to work pretty great and it does allow for interaction in SwiftUI as well as track scrolling.
Very cool! Many thanks @sipersso, I will experiment some more as soon as possible π
I also did try to recreate the Panel in SwiftUI, but none of my attempts where nearly as good as the component that @SCENEE has built. Using the hybrid approach, UIKit panel, but SwiftUI content, makes it really easy to use from a UIKit UIViewController ;)
That's why I didn't try to reimplement the whole thing myself: I want to keep the feeling of a normal scroll view (with the iOS default pull elasticity, etc) π
@sipersso I haven't managed to get the scroll tracking to work with SwiftUI, can you please share your code?
I got ideas from this https://gist.github.com/timothycosta/0d8f64afeca0b6cc29665d87de0d94d2
But had to adapt it a little by setting the content size of the scrollview.
class UIScrollViewViewController: UIViewController {
lazy var scrollView: UIScrollView = {
let v = UIScrollView()
return v
}()
var hostingController: UIHostingController<AnyView> = UIHostingController(rootView: AnyView(Text("Test")))
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.scrollView)
self.pinEdges(of: self.scrollView, to: self.view)
self.hostingController.view.removeFromSuperview()
self.hostingController.willMove(toParent: self)
let width = UIScreen.main.bounds.width
let size = hostingController.view.sizeThatFits(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude))
hostingController.view.frame = CGRect(x: 0, y: 0, width: width, height: size.height)
scrollView.addSubview(hostingController.view)
scrollView.contentSize = CGSize(width: width, height: size.height)
self.hostingController.didMove(toParent: self)
}
func pinEdges(of viewA: UIView, to viewB: UIView) {
viewA.translatesAutoresizingMaskIntoConstraints = false
viewB.addConstraints([
viewA.leadingAnchor.constraint(equalTo: viewB.leadingAnchor),
viewA.trailingAnchor.constraint(equalTo: viewB.trailingAnchor),
viewA.topAnchor.constraint(equalTo: viewB.topAnchor),
viewA.bottomAnchor.constraint(equalTo: viewB.bottomAnchor),
])
}
}
In my floatingpanelcontroller subclass I create an instance of my UIScrollViewViewController and then use this in ViewDidLoad
scrollViewViewController.hostingController = UIHostingController(rootView: AnyView(mySwiftuiView))
set(contentViewController: scrollViewViewController)
track(scrollView: scrollViewViewController.scrollView)
Note that this won't work if your swiftui view changes height. You would have to ajust the contentsize of the scrollview every time this happens.
Thank you @sipersso, I still can't get the scroll tracking even with your code snippets: but it might be me just getting frustrated and giving up too early π
Might try again in a few days π
A full working example similar to the one I posted on the first post would be greatly appreciated π
Hi @zntfdr! Unfortunately I can't share much more than I already have. The rest of my code is non-generic and would be quite an effort for me to provide a more detailed/standalone example
No worries @sipersso, many thanks for the support! π€
@zntfdr Here is working example:
import UIKit
import MapKit
import FloatingPanel
import SwiftUI
class MyFpc: FloatingPanelController {
var scrollViewViewController = UIScrollViewViewController()
override func viewDidLoad() {
super.viewDidLoad()
scrollViewViewController.hostingController = UIHostingController(rootView: vvv)
set(contentViewController: scrollViewViewController)
track(scrollView: scrollViewViewController.scrollView)
}
var vvv: AnyView {
let result1 = VStack {
Text("flk")
Text("flk")
Text("flk")
Text("flk")
List {
Text("flk")
Text("flk")
Text("flk")
Text("flk")
}
}
let result = List {
ForEach((1...100).reversed(), id: \.self) {
Text("\($0)")
}
}
.frame(width: 100, height: 800, alignment: .center)
return AnyView(result)
}
}
class ViewController: UIViewController, MKMapViewDelegate, UISearchBarDelegate, FloatingPanelControllerDelegate {
var fpc: MyFpc!
var hostViewController: UIScrollViewController<MyList>!
@IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Initialize FloatingPanelController
fpc = MyFpc()
fpc.delegate = self
// Initialize FloatingPanelController and add the view
fpc.surfaceView.backgroundColor = .clear
if #available(iOS 11, *) {
fpc.surfaceView.cornerRadius = 9.0
} else {
fpc.surfaceView.cornerRadius = 0.0
}
fpc.surfaceView.shadowHidden = false
hostViewController = UIScrollViewController(rootView: MyList())
// Set a content view controller
fpc.set(contentViewController: hostViewController)
fpc.track(scrollView: hostViewController.scrollView)
setupMapView()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Add FloatingPanel to a view with animation.
fpc.addPanel(toParent: self, animated: true)
}
func setupMapView() {
let center = CLLocationCoordinate2D(latitude: 37.623198015869235,
longitude: -122.43066818432008)
let span = MKCoordinateSpan(latitudeDelta: 0.4425100023575723,
longitudeDelta: 0.28543697435880233)
let region = MKCoordinateRegion(center: center, span: span)
mapView.region = region
mapView.showsCompass = true
mapView.showsUserLocation = true
mapView.delegate = self
}
// MARK: FloatingPanelControllerDelegate
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
switch newCollection.verticalSizeClass {
case .compact:
fpc.surfaceView.borderWidth = 1.0 / traitCollection.displayScale
fpc.surfaceView.borderColor = UIColor.black.withAlphaComponent(0.2)
return SearchPanelLandscapeLayout()
default:
fpc.surfaceView.borderWidth = 0.0
fpc.surfaceView.borderColor = nil
return nil
}
}
}
public class SearchPanelLandscapeLayout: FloatingPanelLayout {
public var initialPosition: FloatingPanelPosition {
return .tip
}
public var supportedPositions: Set<FloatingPanelPosition> {
return [.full, .tip]
}
public func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 16.0
case .tip: return 69.0
default: return nil
}
}
public func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
if #available(iOS 11.0, *) {
return [
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 8.0),
surfaceView.widthAnchor.constraint(equalToConstant: 291),
]
} else {
return [
surfaceView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0),
surfaceView.widthAnchor.constraint(equalToConstant: 291),
]
}
}
public func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat {
return 0.0
}
}
struct MyList: View {
var body: some View {
List {
ForEach((1...100).reversed(), id: \.self) {
Text("\($0)")
}
}
}
}
class UIScrollViewController<Content: View>: UIViewController {
weak var scrollView: UIScrollView!
private let hostingController: UIHostingController<Content>
init(rootView: Content) {
hostingController = UIHostingController<Content>(rootView: rootView)
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
// Add Scroll View.
let scrollView = UIScrollView()
self.scrollView = scrollView
view = scrollView
scrollView.addSubview(hostingController.view)
hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// If we un-comment the following line, fpc will correctly track the
// `scrollView`, but no iteraction would possible with SwiftUI's `View`.
// hostingController.view.isUserInteractionEnabled = false
super.viewDidLoad()
}
}
class UIScrollViewViewController: UIViewController {
lazy var scrollView: UIScrollView = {
let v = UIScrollView()
return v
}()
var hostingController: UIHostingController<AnyView> = UIHostingController(rootView: AnyView(Text("Test")))
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.scrollView)
self.pinEdges(of: self.scrollView, to: self.view)
self.hostingController.view.removeFromSuperview()
self.hostingController.willMove(toParent: self)
let width = UIScreen.main.bounds.width
let size = hostingController.view.sizeThatFits(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude))
hostingController.view.frame = CGRect(x: 0, y: 0, width: width, height: size.height)
scrollView.addSubview(hostingController.view)
scrollView.contentSize = CGSize(width: width, height: size.height)
self.hostingController.didMove(toParent: self)
}
func pinEdges(of viewA: UIView, to viewB: UIView) {
viewA.translatesAutoresizingMaskIntoConstraints = false
viewB.addConstraints([
viewA.leadingAnchor.constraint(equalTo: viewB.leadingAnchor),
viewA.trailingAnchor.constraint(equalTo: viewB.trailingAnchor),
viewA.topAnchor.constraint(equalTo: viewB.topAnchor),
viewA.bottomAnchor.constraint(equalTo: viewB.bottomAnchor),
])
}
}
Many thanks @ramunasjurgilas π Will check it out over the weekend! π
Thank you so much for your work, @zntfdr, @sipersso and @ramunasjurgila ππ Unfortunately now I don't have enough time to take care of this, but I would do it after releasing v2 π
@ramunasjurgilas I'm afraid that your example doesn't work.
As you can see from the video below, scrolling is tracked only when the scroll is outside of the SwiftUI
view.
Thank you for trying! π
@zntfdr just checked the example you provided. You are using List and this has it's own scrollview, which explains why the scrolltracking isn't working. Try using a VStack instead in SwiftUI.
Update: I've added a SwiftUI implementation in #481.
While the example shows a mix of UIKit and SwiftUI, if you use something like SwiftUI-Introspect
, you can build your panel content entirely in SwiftUI, e.g.:
ContentView()
.floatingPanel { proxy in
ScrollView {
// .. your content here.
}
.introspectScrollView { scrollView in
proxy.track(scrollView: scrollView)
}
}
If you'd like to try it out in your app, copy the FloatingPanel
group from the example into your project, and you'll be able to use it as if it was part of the FloatingPanel
library:
Please try it out and let me know how it goes π
For me, this does not work if the content height is dynamic. Anyone else has the same problem?
I'm trying to make the library work with SwiftUI, and was wondering if you have had any success so far.
You can find my attempt here:
Steps:
FloatingPanel
projectViewController.swift
ViewController.swift
content with:Click here to see the source code
Current working example:
Thank you in advance! Federico