I have implemented a UIViewRepresentable for scrollview as I needed the index of scrolled content. Everything works great except an issue where the content of the scroll is not updating.
struct SwiftyUIScrollView<Content>: UIViewRepresentable where Content: View {
typealias UIViewType = Scroll
var content: () -> Content
var pagingEnabled: Bool = false
var hideScrollIndicators: Bool = false
#Binding var shouldUpdate: Bool
#Binding var currentIndex: Int
var onScrollIndexChanged: ((_ index: Int) -> Void)
public init(pagingEnabled: Bool,
hideScrollIndicators: Bool,
currentIndex: Binding<Int>,
shouldUpdate: Binding<Bool>,
#ViewBuilder content: #escaping () -> Content, onScrollIndexChanged: #escaping ((_ index: Int) -> Void)) {
self.content = content
self.pagingEnabled = pagingEnabled
self._currentIndex = currentIndex
self._shouldUpdate = shouldUpdate
self.hideScrollIndicators = hideScrollIndicators
self.onScrollIndexChanged = onScrollIndexChanged
}
func makeUIView(context: UIViewRepresentableContext<SwiftyUIScrollView>) -> UIViewType {
let hosting = UIHostingController(rootView: content())
let view = Scroll(hideScrollIndicators: hideScrollIndicators, isPagingEnabled: pagingEnabled)
view.scrollDelegate = context.coordinator
view.alwaysBounceHorizontal = true
view.addSubview(hosting.view)
makefullScreen(of: hosting.view, to: view)
return view
}
class Coordinator: NSObject, ScrollViewDelegate {
func didScrollToIndex(_ index: Int) {
self.parent.onScrollIndexChanged(index)
}
var parent: SwiftyUIScrollView
init(_ parent: SwiftyUIScrollView) {
self.parent = parent
}
}
func makeCoordinator() -> SwiftyUIScrollView<Content>.Coordinator {
Coordinator(self)
}
func updateUIView(_ uiView: Scroll, context: UIViewRepresentableContext<SwiftyUIScrollView<Content>>) {
if shouldUpdate {
uiView.scrollToIndex(index: currentIndex)
}
}
func makefullScreen(of childView: UIView, to parentView: UIView) {
childView.translatesAutoresizingMaskIntoConstraints = false
childView.leftAnchor.constraint(equalTo: parentView.leftAnchor).isActive = true
childView.rightAnchor.constraint(equalTo: parentView.rightAnchor).isActive = true
childView.topAnchor.constraint(equalTo: parentView.topAnchor).isActive = true
childView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor).isActive = true
}
}
Then created a new class to handle the delegates of a scrollview.
class Scroll: UIScrollView, UIScrollViewDelegate {
var hideScrollIndicators: Bool = false
var scrollDelegate: ScrollViewDelegate?
var tileWidth = 270
var tileMargin = 20
init(hideScrollIndicators: Bool, isPagingEnabled: Bool) {
super.init(frame: CGRect.zero)
showsVerticalScrollIndicator = !hideScrollIndicators
showsHorizontalScrollIndicator = !hideScrollIndicators
delegate = self
self.isPagingEnabled = isPagingEnabled
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let currentIndex = scrollView.contentOffset.x / CGFloat(tileWidth+tileMargin)
scrollDelegate?.didScrollToIndex(Int(currentIndex))
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let currentIndex = scrollView.contentOffset.x / CGFloat(tileWidth+tileMargin)
scrollDelegate?.didScrollToIndex(Int(currentIndex))
}
func scrollToIndex(index: Int) {
let newOffSet = CGFloat(tileWidth+tileMargin) * CGFloat(index)
contentOffset = CGPoint(x: newOffSet, y: contentOffset.y)
}
}
Now to implement the scrollView I used the below code.
#State private var activePageIndex: Int = 0
#State private var shouldUpdateScroll: Bool = false
#State private var tables: [Table] = []
SwiftyUIScrollView(pagingEnabled: false, hideScrollIndicators: true, currentIndex: $activePageIndex, shouldUpdate: $shouldUpdateScroll, content: {
HStack(spacing: 20) {
ForEach(self.tables, id: \.id) { data in
self.cardView(data: data)
}
}
.padding(.horizontal, 30.0)
}, onScrollIndexChanged: { (newIndex) in
shouldUpdateScroll = false
activePageIndex = index
// Your own required handling
})
func getTheNextView(data: Any) -> AnyView {
// Return the required destination View
}
If initially the tables array has 4 objects, updating the array to 5 objects should also include the new view inside the scrollview. But that never updates. Any help would be appreciated.
Related
I'm using Introspect to apply pull to refresh on my ScrollView.
When I pull down to refresh the scroll view jumps back.
I know that this is a common problem that answered already but with SwiftUI and Introspect I don't know where to put the finger.
I know that it can be fixed with scrollDidEndDragging but I can't implement it here.
Does any one have an idea how to solve this issue with this given code?
The code:
private struct PullToRefresh: UIViewRepresentable {
#Binding var isShowing: Bool
let onRefresh: () -> Void
public init(isShowing: Binding<Bool>, onRefresh: #escaping () -> Void) {
_isShowing = isShowing
self.onRefresh = onRefresh
}
public class Coordinator {
let onRefresh: () -> Void
let isShowing: Binding<Bool>
init(onRefresh: #escaping () -> Void, isShowing: Binding<Bool>) {
self.onRefresh = onRefresh
self.isShowing = isShowing
}
#objc func onValueChanged() {
isShowing.wrappedValue = true
onRefresh()
}
}
public func makeUIView(context: UIViewRepresentableContext<PullToRefresh>) -> UIView {
let view = UIView(frame: .zero)
view.isHidden = true
view.isUserInteractionEnabled = false
return view
}
private func scrollView(entry: UIView) -> UIScrollView? {
if let scrollView = Introspect.findAncestor(ofType: UIScrollView.self, from: entry) {
return scrollView
}
guard let viewHost = Introspect.findViewHost(from: entry) else {
return nil
}
return Introspect.previousSibling(containing: UIScrollView.self, from: viewHost)
}
public func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PullToRefresh>) {
DispatchQueue.main.asyncAfter(deadline: .now()) {
guard let scrollView = self.scrollView(entry: uiView) else {
return
}
if let refreshControl = scrollView.refreshControl {
if self.isShowing {
refreshControl.beginRefreshing()
} else {
refreshControl.endRefreshing()
}
return
}
let refreshControl = UIRefreshControl()
refreshControl.addTarget(context.coordinator, action: #selector(Coordinator.onValueChanged), for: .valueChanged)
scrollView.refreshControl = refreshControl
}
}
public func makeCoordinator() -> Coordinator {
return Coordinator(onRefresh: onRefresh, isShowing: $isShowing)
}
}
Usage:
extension View {
public func pullToRefresh(isShowing: Binding<Bool>, onRefresh: #escaping () -> Void) -> some View {
return overlay(
PullToRefresh(isShowing: isShowing, onRefresh: onRefresh)
.frame(width: 0, height: 0)
, alignment: .center)
}
}
I have a SwiftUI UIViewControllerRepresentable that I've embedded into a ScrollView (see below)
import SwiftUI
struct ContactView: View {
#State private var text = ""
var body: some View {
ScrollView{
TextFieldView(text: $text, dismissKeyboardCallback: nil)
}
}
}
When I tap the TextField nothing happens. If I replace the parent ScrollView with a VStack, the TextFieldRepresentable is able to receive focus as normal (a soft keyboard appears and I'm able to input data). Is this a bug, or am I doing something incorrect? The UIViewControllerRepresentable is included below if it helps at all:
import SwiftUI
struct TextFieldView: View {
var text: Binding<String>
var onDismissKeyboard: (() -> Void)?
var body: some View {
TextFieldRepresentable(text: text, dismissKeyboardCallback: self.onDismissKeyboard)
.frame(height: 32, alignment: .leading)
}
}
struct TextFieldRepresentable: UIViewControllerRepresentable {
let dismissKeyboardCallback: (() -> Void)?
let viewController: TextFieldViewController
init (text: Binding<String>, dismissKeyboardCallback: (() -> Void)?) {
self.dismissKeyboardCallback = dismissKeyboardCallback
self.viewController = TextFieldViewController(text: text, onDismiss: dismissKeyboardCallback)
}
func makeUIViewController(context: Context) -> UIViewController {
return viewController
}
func updateUIViewController(_ viewController: UIViewController, context: Context) {}
}
And the TextFieldViewController itself:
class TextFieldViewController: UIViewController, UITextFieldDelegate {
let text: Binding<String>?
let onDismiss: (() -> Void)?
init (text: Binding<String>, onDismiss: (() -> Void)?) {
self.text = text
self.onDismiss = onDismiss
super.init( nibName: "TextField", bundle: Bundle.main)
}
required init?(coder: NSCoder) {
self.text = nil
self.onDismiss = nil
super.init(coder: coder)
}
fileprivate func getTextField() -> UITextField? {
return view.subviews.first as? UITextField
}
override func viewDidLoad() {
let textField = self.getTextField()
textField?.delegate = self
let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
toolbar.barStyle = UIBarStyle.default
toolbar.items = [UIBarButtonItem(title: "Done", style: UIBarButtonItem.Style.done, target: self, action: #selector(self.onSet))]
textField?.inputAccessoryView = toolbar
}
#objc private func onSet() {
let textField = self.getTextField()
textField?.resignFirstResponder()
self.text?.wrappedValue = textField?.text ?? ""
self.onDismiss?()
}
}
I'm trying to set a certain size for a popover or to make it adapt its content
I tried to change the frame for the view from popover, but it does not seem to work
Button("Popover") {
self.popover7.toggle()
}.popover(isPresented: self.$popover7, arrowEdge: .bottom) {
PopoverView().frame(width: 100, height: 100, alignment: .center)
}
I'd like to achieve this behaviour I found in Calendar app in iPad
The solution by #ccwasden works very well. I extended his work by making it more "natural" in terms of SwiftUI. Also, this version utilizes sizeThatFits method, so you don't have to specify the size of the popover content.
struct PopoverViewModifier<PopoverContent>: ViewModifier where PopoverContent: View {
#Binding var isPresented: Bool
let onDismiss: (() -> Void)?
let content: () -> PopoverContent
func body(content: Content) -> some View {
content
.background(
Popover(
isPresented: self.$isPresented,
onDismiss: self.onDismiss,
content: self.content
)
)
}
}
extension View {
func popover<Content>(
isPresented: Binding<Bool>,
onDismiss: (() -> Void)? = nil,
content: #escaping () -> Content
) -> some View where Content: View {
ModifiedContent(
content: self,
modifier: PopoverViewModifier(
isPresented: isPresented,
onDismiss: onDismiss,
content: content
)
)
}
}
struct Popover<Content: View> : UIViewControllerRepresentable {
#Binding var isPresented: Bool
let onDismiss: (() -> Void)?
#ViewBuilder let content: () -> Content
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self, content: self.content())
}
func makeUIViewController(context: Context) -> UIViewController {
return UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
context.coordinator.host.rootView = self.content()
if self.isPresented, uiViewController.presentedViewController == nil {
let host = context.coordinator.host
host.preferredContentSize = host.sizeThatFits(in: CGSize(width: Int.max, height: Int.max))
host.modalPresentationStyle = UIModalPresentationStyle.popover
host.popoverPresentationController?.delegate = context.coordinator
host.popoverPresentationController?.sourceView = uiViewController.view
host.popoverPresentationController?.sourceRect = uiViewController.view.bounds
uiViewController.present(host, animated: true, completion: nil)
}
}
class Coordinator: NSObject, UIPopoverPresentationControllerDelegate {
let host: UIHostingController<Content>
private let parent: Popover
init(parent: Popover, content: Content) {
self.parent = parent
self.host = UIHostingController(rootView: content)
}
func presentationControllerWillDismiss(_ presentationController: UIPresentationController) {
self.parent.isPresented = false
if let onDismiss = self.parent.onDismiss {
onDismiss()
}
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
}
I got it to work on iOS with a custom UIViewRepresentable. Here is what the usage looks like:
struct Content: View {
#State var open = false
#State var popoverSize = CGSize(width: 300, height: 300)
var body: some View {
WithPopover(
showPopover: $open,
popoverSize: popoverSize,
content: {
Button(action: { self.open.toggle() }) {
Text("Tap me")
}
},
popoverContent: {
VStack {
Button(action: { self.popoverSize = CGSize(width: 300, height: 600)}) {
Text("Increase size")
}
Button(action: { self.open = false}) {
Text("Close")
}
}
})
}
}
And here is a gist with the source for WithPopover
macOS-only
Here is how to change frame of popover dynamically... for simplicity it is w/o animation, it is up to you.
struct TestCustomSizePopover: View {
#State var popover7 = false
var body: some View {
VStack {
Button("Popover") {
self.popover7.toggle()
}.popover(isPresented: self.$popover7, arrowEdge: .bottom) {
PopoverView()
}
}.frame(width: 800, height: 600)
}
}
struct PopoverView: View {
#State var adaptableHeight = CGFloat(100)
var body: some View {
VStack {
Text("Popover").padding()
Button(action: {
self.adaptableHeight = 300
}) {
Text("Button")
}
}
.frame(width: 100, height: adaptableHeight)
}
}
I have been having trouble getting the ListCollectionViewLayout, the stock layout provided by IGListKit, to work together with Texture (formerly AsyncDisplayKit).
This is my Collection View Controller:
let collectionNode: ASCollectionNode!
var refreshControl : UIRefreshControl?
var layout: ListCollectionViewLayout
var pageTitle: String?
var feedItems: [FeedItem] = [FeedItem]()
lazy var adapter: ListAdapter = {
return ListAdapter(updater: ListAdapterUpdater(), viewController: self, workingRangeSize: 1)
}()
init() {
layout = ListCollectionViewLayout.init(stickyHeaders: false, scrollDirection: .vertical, topContentInset: 0, stretchToEdge: false)
self.collectionNode = ASCollectionNode(collectionViewLayout: layout)
super.init(node: self.collectionNode)
self.adapter.setASDKCollectionNode(self.collectionNode)
self.adapter.dataSource = self
self.collectionNode.alwaysBounceVertical = true
refreshControl = UIRefreshControl()
refreshControl?.addTarget(self, action: #selector(refreshContent), for: .valueChanged)
self.collectionNode.view.addSubview(refreshControl!)
}
This is the Section Controller:
class HashtagSectionController: ListSectionController, ASSectionController {
weak var delegate: HashtagDataDelegate?
var pushViewDelegate: PushViewControllerDelegate?
var pushUserDelegate: PushUsernameDelegate?
var isLoading: Bool
func nodeForItem(at index: Int) -> ASCellNode {
guard let feedItem = object else { return ASCellNode() }
let node = DiscoverCellNode(post: feedItem.post, user: feedItem.user)
DispatchQueue.main.async {
node.contentNode.delegate = self
}
return node
}
override init() {
self.isLoading = false
super.init()
self.inset = UIEdgeInsets(top: 10, left: 0, bottom: 20, right: 0)
}
var object: FeedItem?
func nodeBlockForItem(at index: Int) -> ASCellNodeBlock {
guard let feedItem = object else { return {
return ASCellNode()
}
}
return {
let node = DiscoverCellNode(post: feedItem.post, user: feedItem.user)
DispatchQueue.main.async {
node.contentNode.delegate = self
}
return node
}
}
override func numberOfItems() -> Int {
return 1
}
override func didUpdate(to object: Any) {
self.object = object as? FeedItem
}
override func didSelectItem(at index: Int) {
guard let feedItem = object else { return }
pushViewDelegate?.pushViewController(post: feedItem.post, user: feedItem.user)
}
override func sizeForItem(at index: Int) -> CGSize {
return ASIGListSectionControllerMethods.sizeForItem(at: index)
//return CGSize(width: 120, height: 120)
}
override func cellForItem(at index: Int) -> UICollectionViewCell {
return ASIGListSectionControllerMethods.cellForItem(at: index, sectionController: self)
}
}
So I am unsure as to why this isn't working. Am I missing a Delegate the ListCollectionLayout needs in order to work. I get an error stating "layoutSize is invalid and unsafe to provide to CoreAnimation".
I ended up not using the listkit and decided to use just the diffing algorithm with the Texture collection view.
So I am trying to create a custom pagination scrollView. I have been able to create that wrapper and the content inside that wrapper consists of a custom View. Inside that custom View i have got two NavigationLink buttons when pressed should take users to two different Views.
Those NavigationLink buttons are not working.
The scrollViewWrapper is inside a NavigationView. I created a test button which is just a simple Button and that seems to work. So there is something that I am not doing correctly with NavigationLink and custom UIViewControllerRepresentable.
This is where I am using the custom wrapper.
NavigationView {
UIScrollViewWrapper {
HStack(spacing: 0) {
ForEach(self.onboardingDataArray, id: \.id) { item in
OnboardingView(onboardingData: item)
.frame(width: geometry.size.width, height: geometry.size.height)
}
}
}.frame(width: geometry.size.width, height: geometry.size.height)
.background(Color.blue)
}
The onboarding view:
struct OnboardingView: View {
var onboardingData: OnboardingModel
var body: some View {
GeometryReader { geometry in
VStack(spacing: 10) {
Spacer()
Image("\(self.onboardingData.image)")
.resizable()
.frame(width: 300, height: 300)
.aspectRatio(contentMode: ContentMode.fit)
.clipShape(Circle())
.padding(20)
Text("\(self.onboardingData.titleText)")
.frame(width: geometry.size.width, height: 20, alignment: .center)
.font(.title)
Text("\(self.onboardingData.descriptionText)")
.lineLimit(nil)
.padding(.leading, 15)
.padding(.trailing, 15)
.font(.system(size: 16))
.frame(width: geometry.size.width, height: 50, alignment: .center)
.multilineTextAlignment(.center)
Spacer(minLength: 20)
if self.onboardingData.showButton ?? false {
VStack {
Button(action: {
print("Test")
}) {
Text("Test Button")
}
NavigationLink(destination: LogInView()) {
Text("Login!")
}
NavigationLink(destination: SignUpView()) {
Text("Sign Up!")
}
}
}
Spacer()
}
}
}
}
The custom ScrollView Wrapper code:
struct UIScrollViewWrapper<Content: View>: UIViewControllerRepresentable {
var content: () -> Content
init(#ViewBuilder content: #escaping () -> Content) {
self.content = content
}
func makeUIViewController(context: Context) -> UIScrollViewController {
let vc = UIScrollViewController()
vc.hostingController.rootView = AnyView(self.content())
return vc
}
func updateUIViewController(_ viewController: UIScrollViewController, context: Context) {
viewController.hostingController.rootView = AnyView(self.content())
}
}
class UIScrollViewController: UIViewController {
lazy var scrollView: UIScrollView = {
let view = UIScrollView()
view.isPagingEnabled = true
return view
}()
var hostingController: UIHostingController<AnyView> = UIHostingController(rootView: AnyView(EmptyView()))
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.scrollView)
self.pinEdges(of: self.scrollView, to: self.view)
self.hostingController.willMove(toParent: self)
self.scrollView.addSubview(self.hostingController.view)
self.pinEdges(of: self.hostingController.view, to: self.scrollView)
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),
])
}
As the other answers have stated, there is an issue with putting the NavigationLink inside the UIViewControllerRepresentable.
I solved this by wrapping my UIViewControllerRepresentable and a NavigationLink inside a View and programmatically activating the NavigationLink from inside the UIViewControllerRepresentable.
For example:
struct MyView: View
{
#State var destination: AnyView? = nil
#State var is_active: Bool = false
var body: some View
{
ZStack
{
MyViewControllerRepresentable( self )
NavigationLink( destination: self.destination, isActive: self.$is_active )
{
EmptyView()
}
}
}
func goTo( destination: AnyView )
{
self.destination = destination
self.is_active = true
}
}
In my case, I passed the MyView instance to the UIViewController that my MyViewControllerRepresentable is wrapping, and called my goTo(destination:AnyView) method when a button was clicked.
The difference between our cases is that my UIViewController was my own class written with UIKit (compared to a UIHostingController). In the case that you're using a UIHostingController, you could probably use a shared ObservableObject containing the destination and is_active variables. You'd change your 2 NavigationLinks to Buttons having the action methods change the ObservableObject's destination and is_active variables.
This happens because you use a UIViewControllerRepresentable instead of UIViewRepresentable. I guess the UIScrollViewController keeps the destination controller from being presented by the current controller.
Try the code above instead:
import UIKit
import SwiftUI
struct ScrollViewWrapper<Content>: UIViewRepresentable where Content: View{
func updateUIView(_ uiView: UIKitScrollView, context: UIViewRepresentableContext<ScrollViewWrapper<Content>>) {
}
typealias UIViewType = UIKitScrollView
let content: () -> Content
var showsIndicators : Bool
public init(_ axes: Axis.Set = .vertical, showsIndicators: Bool = true, #ViewBuilder content: #escaping () -> Content) {
self.content = content
self.showsIndicators = showsIndicators
}
func makeUIView(context: UIViewRepresentableContext<ScrollViewWrapper>) -> UIViewType {
let hosting = UIHostingController(rootView: AnyView(content()))
let width = UIScreen.main.bounds.width
let size = hosting.view.sizeThatFits(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude))
hosting.view.frame = CGRect(x: 0, y: 0, width: width, height: size.height)
let view = UIKitScrollView()
view.delegate = view
view.alwaysBounceVertical = true
view.addSubview(hosting.view)
view.contentSize = CGSize(width: width, height: size.height)
return view
}
}
class UIKitScrollView: UIScrollView, UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset) // Do whatever you want.
}
}
This is an extension to the above solution that never scrolls the inner content.
I was into a similar problem. I have figured out that the problem is with the UIViewControllerRepresentable. Instead use UIViewRepresentable, although I am not sure what the issue is. I was able to get the navigationlink work using the below code.
struct SwiftyUIScrollView<Content>: UIViewRepresentable where Content: View {
typealias UIViewType = Scroll
var content: () -> Content
var pagingEnabled: Bool = false
var hideScrollIndicators: Bool = false
#Binding var shouldUpdate: Bool
#Binding var currentIndex: Int
var onScrollIndexChanged: ((_ index: Int) -> Void)
public init(pagingEnabled: Bool,
hideScrollIndicators: Bool,
currentIndex: Binding<Int>,
shouldUpdate: Binding<Bool>,
#ViewBuilder content: #escaping () -> Content, onScrollIndexChanged: #escaping ((_ index: Int) -> Void)) {
self.content = content
self.pagingEnabled = pagingEnabled
self._currentIndex = currentIndex
self._shouldUpdate = shouldUpdate
self.hideScrollIndicators = hideScrollIndicators
self.onScrollIndexChanged = onScrollIndexChanged
}
func makeUIView(context: UIViewRepresentableContext<SwiftyUIScrollView>) -> UIViewType {
let hosting = UIHostingController(rootView: content())
let view = Scroll(hideScrollIndicators: hideScrollIndicators, isPagingEnabled: pagingEnabled)
view.scrollDelegate = context.coordinator
view.alwaysBounceHorizontal = true
view.addSubview(hosting.view)
makefullScreen(of: hosting.view, to: view)
return view
}
class Coordinator: NSObject, ScrollViewDelegate {
func didScrollToIndex(_ index: Int) {
self.parent.onScrollIndexChanged(index)
}
var parent: SwiftyUIScrollView
init(_ parent: SwiftyUIScrollView) {
self.parent = parent
}
}
func makeCoordinator() -> SwiftyUIScrollView<Content>.Coordinator {
Coordinator(self)
}
func updateUIView(_ uiView: Scroll, context: UIViewRepresentableContext<SwiftyUIScrollView<Content>>) {
if shouldUpdate {
uiView.scrollToIndex(index: currentIndex)
}
}
func makefullScreen(of childView: UIView, to parentView: UIView) {
childView.translatesAutoresizingMaskIntoConstraints = false
childView.leftAnchor.constraint(equalTo: parentView.leftAnchor).isActive = true
childView.rightAnchor.constraint(equalTo: parentView.rightAnchor).isActive = true
childView.topAnchor.constraint(equalTo: parentView.topAnchor).isActive = true
childView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor).isActive = true
}
}
Then create a new class to handle the delegates of a scrollview. You can include the below code into the UIViewRepresentable as well. But I prefer keeping it separated for a clean code.
class Scroll: UIScrollView, UIScrollViewDelegate {
var hideScrollIndicators: Bool = false
var scrollDelegate: ScrollViewDelegate?
var tileWidth = 270
var tileMargin = 20
init(hideScrollIndicators: Bool, isPagingEnabled: Bool) {
super.init(frame: CGRect.zero)
showsVerticalScrollIndicator = !hideScrollIndicators
showsHorizontalScrollIndicator = !hideScrollIndicators
delegate = self
self.isPagingEnabled = isPagingEnabled
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let currentIndex = scrollView.contentOffset.x / CGFloat(tileWidth+tileMargin)
scrollDelegate?.didScrollToIndex(Int(currentIndex))
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let currentIndex = scrollView.contentOffset.x / CGFloat(tileWidth+tileMargin)
scrollDelegate?.didScrollToIndex(Int(currentIndex))
}
func scrollToIndex(index: Int) {
let newOffSet = CGFloat(tileWidth+tileMargin) * CGFloat(index)
contentOffset = CGPoint(x: newOffSet, y: contentOffset.y)
}
}
Now to implement the scrollView use the below code.
#State private var activePageIndex: Int = 0
#State private var shouldUpdateScroll: Bool = false
SwiftyUIScrollView(pagingEnabled: false, hideScrollIndicators: true, currentIndex: $activePageIndex, shouldUpdate: $shouldUpdateScroll, content: {
HStack(spacing: 20) {
ForEach(self.data, id: \.id) { data in
NavigationLink(destination: self.getTheNextView(data: data)) {
self.cardView(data: data)
}
}
}
.padding(.horizontal, 30.0)
}, onScrollIndexChanged: { (newIndex) in
shouldUpdateScroll = false
activePageIndex = index
// Your own required handling
})
func getTheNextView(data: Any) -> AnyView {
// Return the required destination View
}
Don't forget to add your hosting controller as a child.
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.scrollView)
self.pinEdges(of: self.scrollView, to: self.view)
addChild(self.hostingController)
self.hostingController.willMove(toParent: self)
self.scrollView.addSubview(self.hostingController.view)
self.pinEdges(of: self.hostingController.view, to: self.scrollView)
self.hostingController.didMove(toParent: self)
}
Did you set up the Triggered Segues? If you are using Xcode you can right click the button you created in the main storyboard. If it's not set up you can go to the connections inspector on the top right sidebar where you can find the File inspector,Identity inspector, Attributes inspector... and specify the action of what you want your button to do.