← Back to list

Resolving the Issue: SwiftUI TabView Support for iOS 13

Fix Issue for iOS 13

Jerry PM in Stackademic · 2024-11-13 02:36 · 1 claps · 3.1 min read paywalled
#bugs #ios-13 #swift #swiftui #custom
Open on Medium ↗
Wiki topics: 📱 · Mobile Development

Resolving the Issue: SwiftUI TabView Support for iOS 13

Fix Issue for iOS 13

If you’re facing the same issue as I did, here’s the solution:

In my iOS project, I needed to support iOS 13. Unfortunately, many newer SwiftUI components, including the updated TabView, require iOS 14 or later, creating compatibility issues with my work. One such issue was implementing a card slider with an indicator, which TabView typically handles but isn't available for iOS 13.

To work around this, I created a custom ScrollablePageView that replicates the functionality of TabView as closely as possible. This custom view allows my project to maintain compatibility with iOS 13 while achieving the desired sliding indicator effect.

This Available only on iOS 14 and above.

//#available(iOS 14.0, *)
TabView(selection: $currentPage) {
    ForEach(0 ..< viewModel.investingSelections.count, id: \.self) { index in
        // put yout view here
        InvestingSelectionContentView(model: viewModel.investingSelections)
            .tag(index)
    }
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.frame(height: 320)

Result error look like this, it caanot use in if minimum deployment still iOS 13.

Here’s the complete code for a custom TabView that works with iOS 13. Feel free to copy this, and then simply call it wherever you need to use it.

This solution for TabView for iOS 13 like this

import SwiftUI

// Custom UIScrollView wrapper for iOS 13
struct ScrollablePageView<Content: View>: UIViewControllerRepresentable {
    @Binding var currentPage: Int
    let pages: Int
    let content: (Int) -> Content

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIViewController(context: Context) -> UIPageViewController {
        let pageViewController = UIPageViewController(
            transitionStyle: .scroll,
            navigationOrientation: .horizontal,
            options: nil
        )
        pageViewController.dataSource = context.coordinator
        pageViewController.delegate = context.coordinator

        // Create initial view controller
        let initialVC = UIHostingController(rootView: content(currentPage))
        pageViewController.setViewControllers(
            [initialVC],
            direction: .forward,
            animated: true
        )

        return pageViewController
    }

    func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
        context.coordinator.parent = self

        if let currentVC = pageViewController.viewControllers?.first,
           let currentIndex = context.coordinator.indexForViewController(currentVC),
           currentIndex != currentPage
        {
            let newVC = context.coordinator.createViewController(for: currentPage)
            let direction: UIPageViewController.NavigationDirection = currentIndex < currentPage ? .forward : .reverse

            pageViewController.setViewControllers(
                [newVC],
                direction: direction,
                animated: true
            )
        }
    }

    class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
        var parent: ScrollablePageView
        private var controllers: [UIHostingController<Content>] = []

        init(_ pageViewController: ScrollablePageView) {
            self.parent = pageViewController
            super.init()
        }

        // MARK: - UIPageViewControllerDataSource

        func pageViewController(
            _ pageViewController: UIPageViewController,
            viewControllerBefore viewController: UIViewController
        ) -> UIViewController? {
            guard let index = indexForViewController(viewController) else { return nil }
            if index == 0 { return nil }
            return createViewController(for: index - 1)
        }

        func pageViewController(
            _ pageViewController: UIPageViewController,
            viewControllerAfter viewController: UIViewController
        ) -> UIViewController? {
            guard let index = indexForViewController(viewController) else { return nil }
            if index + 1 >= parent.pages { return nil }
            return createViewController(for: index + 1)
        }

        // MARK: - UIPageViewControllerDelegate

        func pageViewController(
            _ pageViewController: UIPageViewController,
            didFinishAnimating finished: Bool,
            previousViewControllers: [UIViewController],
            transitionCompleted completed: Bool
        ) {
            if finished,
               completed,
               let visibleVC = pageViewController.viewControllers?.first,
               let index = indexForViewController(visibleVC)
            {
                DispatchQueue.main.async {
                    self.parent.currentPage = index
                }
            }
        }

        // MARK: - Helper methods

        func createViewController(for index: Int) -> UIViewController {
            let hostingController = UIHostingController(rootView: parent.content(index))
            hostingController.view.tag = index // Use tag to store index
            return hostingController
        }

        func indexForViewController(_ viewController: UIViewController) -> Int? {
            if let hostingController = viewController as? UIHostingController<Content> {
                return hostingController.view.tag
            }
            return nil
        }
    }
}

How to use the custom TabView


if #available(iOS 14.0, *) {
    TabView(selection: $currentPage) {
        ForEach(0 ..< viewModel.investingSelections.count, id: \.self) { index in
            InvestingSelectionContentView(model: viewModel.investingSelections)
                .tag(index)
        }
    }
    .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
    .frame(height: 320)
} else {
    // for iOS 13
    ScrollablePageView(
        currentPage: $currentPage,
        pages: viewModel.investingSelections.count
    ) { index in
      // put yout view here
        InvestingSelectionContentView(model: viewModel.investingSelections)

    }
    .frame(height: 320)
}

This Preview using Custom TabView

Result

Result

Using this solution alone will work smoothly on the latest iOS versions as well, so there’s no need to create two separate versions.

Thank you for reading! If you enjoyed it, please consider clapping and following for more updates! 👏

Stackademic 🎓

Thank you for reading until the end. Before you go:


메타데이터
post_id
18e5ced57ddd
slug
resolving-the-issue-swiftui-tabview-support-for-ios-13-18e5ced57ddd
url
https://blog.stackademic.com/resolving-the-issue-swiftui-tabview-support-for-ios-13-18e5ced57ddd
canonical_url
https://blog.stackademic.com/resolving-the-issue-swiftui-tabview-support-for-ios-13-18e5ced57ddd
author_url
https://medium.com/@21zerixpm
status
ok
fetched_at
2026-07-22 05:00:42