【问题标题】:UIPageViewController: Reverse .scroll animation direction for right-to-left languagesUIPageViewController:从右到左的语言反转 .scroll 动画方向
【发布时间】:2022-01-13 17:38:18
【问题描述】:

在使用UIPageViewController.TransitionStyle.scroll 时,是否可以将UIPageViewController 的动画方向反转为从右到左的语言?

左右滑动可以正确反转从右到左,但我的下一个按钮解决方案的动画方向错误(动画就像从左到右一样)。

我目前的解决方案是设置pageControl.semanticContentAttribute = .forceLeftToRight,并反转viewModels,以便从右到左的索引0是最后一个索引(viewModels.count - 1),以及反转动画方向+ viewControllerBefore/viewControllerAfter,例如:

  • viewControllerBefore 变为:nextIndex = isRightToLeft ? vc.index - 1 : vc.index + 1(反之亦然,viewControllerAfter
  • nextTappedinDirection 参数为 transitionFrom 更改为 isRightToLeft ? .reverse : .forward
  • 更改loadFirstPage 以将startIndex 设置为numberOfPages - 1(又名viewModels.count - 1)而不是0
  • 反转视图模型,使索引 0 为 viewModels.count - 1(在 init 中):
         if isRightToLeft {
             viewModels = viewModels.map({ viewModel in
                 ViewModel(index: (numberOfPages - viewModel.index - 1), text: viewModel.text, color: viewModel.color)
             }).reversed()
    

但我想要一个简单地改变开箱即用动画方向的解决方案,就像在将spineLocation 更改为UIPageViewController.TransitionStyle.pageCurl 时似乎是可能的,比如this question 状态。

动画

请注意,对于How it is by default,页面来自右侧,但页面控制指示器向相反(预期的)方向移动。

How it is by default Expected Functionality

默认实现:

import UIKit

struct ViewModel {
    var index: Int
    var text: String
    var color: UIColor
}

class DummyViewController : UIViewController {
    let label = UILabel()
    
    let vm: ViewModel
    
    let index: Int
    
    init(vm: ViewModel) {
        self.vm = vm
        self.index = vm.index
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    
    override func loadView() {
        let view = UIView()
        view.backgroundColor = vm.color
        
        let label = UILabel()
        label.frame = CGRect(x: 20, y: 200, width: 200, height: 20)
        label.textColor = .black
        label.text = vm.text
        
        view.addSubview(label)
        
        self.view = view
    }
}

class ViewController : UIViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
    let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
    
    let pageControl = UIPageControl()
    
    let nextButton = UIButton()
    
    var isRightToLeft: Bool {
        traitCollection.layoutDirection == .rightToLeft
    }
    
    var currentIndex: Int = 0 {
        didSet {
            pageControl.currentPage = currentIndex
        }
    }
    
    var numberOfPages: Int { return viewModels.count }
    
    var viewModels: [ViewModel] = [
        ViewModel(index: 0, text: "First", color: .red),
        ViewModel(index: 1, text: "Second", color: .blue),
        ViewModel(index: 2, text: "Third", color: .green),
    ]
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        
        pageViewController.dataSource = self
        pageViewController.delegate = self
        
        pageControl.currentPage = currentIndex
        pageControl.numberOfPages = numberOfPages
        
        nextButton.setTitle("Next", for: .normal)
        nextButton.setTitleColor(.black, for: .normal)
    }
    
    override func viewDidLoad() {
        addChild(pageViewController)
        
        pageViewController.didMove(toParent: self)
        
        view.addSubview(pageViewController.view)
        view.addSubview(pageControl)
        view.addSubview(nextButton)
        
        view.subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
        
        NSLayoutConstraint.activate([
            pageViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
            pageViewController.view.leftAnchor.constraint(equalTo: view.leftAnchor),
            pageViewController.view.rightAnchor.constraint(equalTo: view.rightAnchor),
            pageViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            pageControl.centerYAnchor.constraint(equalTo: nextButton.centerYAnchor),
            
            nextButton.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            nextButton.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor)
        ])
        
        nextButton.addTarget(self, action: #selector(nextTapped), for: .primaryActionTriggered)
        
        loadFirstPage()
    }
    
    func loadFirstPage() {
        let startIndex: Int
        startIndex = 0
        
        guard let start = viewControllerAtIndex(startIndex) else {
            return
        }
        
        pageViewController.setViewControllers([start], direction: .forward, animated: true, completion: nil)
    }
    
    func transitionFrom(index: Int, inDirection direction: UIPageViewController.NavigationDirection) {
        let nextIndex = direction == .forward ? index + 1 : index - 1
        
        guard let next = viewControllerAtIndex(nextIndex) else {
            return
        }
        pageViewController.setViewControllers([next], direction: direction, animated: true, completion: { finished in
            self.pageViewController(self.pageViewController, didFinishAnimating: finished, previousViewControllers: [], transitionCompleted: finished)
        })
    }
    
    func viewControllerAtIndex(_ index: Int) -> DummyViewController? {
        guard index >= 0 && index < numberOfPages  else { return nil }
        let viewModel = viewModels[index]
        return DummyViewController(vm: viewModel)
    }
    
    @objc
    func nextTapped(_ sender: UIButton) {
        guard currentIndex < numberOfPages - 1 else {
            print("ending because \(currentIndex)")
            return
        }
        
        let direction: UIPageViewController.NavigationDirection = .forward
        transitionFrom(index: currentIndex, inDirection: direction)
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        guard let vc = viewController as? DummyViewController else { return nil }
        let nextIndex: Int = vc.index - 1
        return viewControllerAtIndex(nextIndex)
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        guard let vc = viewController as? DummyViewController else { return nil }
        let nextIndex: Int = vc.index + 1
        return viewControllerAtIndex(nextIndex)
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        guard let viewController = pageViewController.viewControllers?.first as? DummyViewController else { return }
        currentIndex = viewController.index // Update currentIndex, which updates pageControl.currentPage
    }
}

手动反转以支持从右到左(我试图避免的解决方案)。

排除了ViewModelDummyViewController,因为它们没有改变。

import UIKit

class ViewController : UIViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
    let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
    
    let pageControl = UIPageControl()
    
    let nextButton = UIButton()
    
    var isRightToLeft: Bool {
        traitCollection.layoutDirection == .rightToLeft
    }
    
    var currentIndex: Int = 0 {
        didSet {
            pageControl.currentPage = currentIndex
        }
    }
    
    var numberOfPages: Int {
        return viewModels.count
    }
    
    var viewModels: [ViewModel] = [
        ViewModel(index: 0, text: "First", color: .red),
        ViewModel(index: 1, text: "Second", color: .blue),
        ViewModel(index: 2, text: "Third", color: .green),
    ]
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        
        pageViewController.dataSource = self
        pageViewController.delegate = self
        
        pageControl.currentPage = currentIndex
        pageControl.numberOfPages = numberOfPages
        
        pageControl.semanticContentAttribute = .forceLeftToRight
        
        nextButton.setTitle("Next", for: .normal)
        nextButton.setTitleColor(.black, for: .normal)
        
        if isRightToLeft { // HERE
            viewModels = viewModels.map({ viewModel in
                ViewModel(index: (numberOfPages - viewModel.index - 1), text: viewModel.text, color: viewModel.color)
            }).reversed()
        }
    }
    
    override func viewDidLoad() {
        addChild(pageViewController)
        
        pageViewController.didMove(toParent: self)
        
        view.addSubview(pageViewController.view)
        view.addSubview(pageControl)
        view.addSubview(nextButton)
        
        view.subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
        
        NSLayoutConstraint.activate([
            pageViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
            pageViewController.view.leftAnchor.constraint(equalTo: view.leftAnchor),
            pageViewController.view.rightAnchor.constraint(equalTo: view.rightAnchor),
            pageViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            pageControl.centerYAnchor.constraint(equalTo: nextButton.centerYAnchor),
            
            nextButton.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            nextButton.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor)
        ])
        
        nextButton.addTarget(self, action: #selector(nextTapped), for: .primaryActionTriggered)
        
        loadFirstPage()
    }
    
    func loadFirstPage() {
        let startIndex: Int
        if isRightToLeft { // HERE
            startIndex = numberOfPages - 1
            currentIndex = startIndex
        } else {
            startIndex = 0
        }
        
        guard let start = viewControllerAtIndex(startIndex) else {
            return
        }
        
        pageViewController.setViewControllers([start], direction: .forward, animated: true, completion: nil)
    }
    
    func transitionFrom(index: Int, inDirection direction: UIPageViewController.NavigationDirection) { // UNCHANGED
        let nextIndex = direction == .forward ? index + 1 : index - 1
        
        guard let next = viewControllerAtIndex(nextIndex) else {
            return
        }
        pageViewController.setViewControllers([next], direction: direction, animated: true, completion: { finished in
            self.pageViewController(self.pageViewController, didFinishAnimating: finished, previousViewControllers: [], transitionCompleted: finished)
        })
    }
    
    func viewControllerAtIndex(_ index: Int) -> DummyViewController? {
        guard index >= 0 && index < numberOfPages  else { return nil }
        let viewModel = viewModels[index]
        return DummyViewController(vm: vm)
    }
    
    @objc
    func nextTapped(_ sender: UIButton) {
        guard (currentIndex < numberOfPages - 1 && !isRightToLeft) || (currentIndex > 0 && isRightToLeft) else { // HERE
            return
        }
        let direction: UIPageViewController.NavigationDirection = isRightToLeft ? .reverse : .forward // HERE
        transitionFrom(index: currentIndex, inDirection: direction)
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        guard let vc = viewController as? DummyViewController else {
            return nil
        }
        
        let nextIndex: Int
        if isRightToLeft { // HERE
            nextIndex = vc.index + 1
        } else {
            nextIndex = vc.index - 1
        }
        
        return viewControllerAtIndex(nextIndex)
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        guard let vc = viewController as? DummyViewController else {
            return nil
        }
        
        let nextIndex: Int
        if isRightToLeft { // HERE
            nextIndex = vc.index - 1
        } else {
            nextIndex = vc.index + 1
        }
        
        return viewControllerAtIndex(nextIndex)
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        guard let viewController = pageViewController.viewControllers?.first as? DummyViewController else {
            return
        }
        
        currentIndex = viewController.index
    }
}


【问题讨论】:

    标签: ios swift uipageviewcontroller right-to-left


    【解决方案1】:

    事实证明这比我想象的要简单得多 - 设置 direction 只会改变 动画 方向,它不会改变索引/视图控制器的运行方向呈现出来。

    因此,解决方案只需将transitionFrom 更改为:

        func transitionTo(nextIndex: Int, inDirection direction: UIPageViewController.NavigationDirection) {
            // Removed the `next` setting here in favor of getting it passed from `nextTapped`
            guard let next = viewControllerAtIndex(nextIndex) else {
                return
            }
            pageViewController.setViewControllers([next], direction: direction, animated: true, completion: { finished in
                self.pageViewController(self.pageViewController, didFinishAnimating: finished, previousViewControllers: [], transitionCompleted: finished)
            })
        }
    

    并将nextTapped 更改为:

        @objc
        func nextTapped(_ sender: UIButton) {
            guard currentIndex < numberOfPages - 1 else { return }
            
            let direction: UIPageViewController.NavigationDirection = isRightToLeft ? .reverse : .forward // This is key
            transitionTo(nextIndex: currentIndex + 1, inDirection: direction)
        }
    

    另一种侵入性较小的解决方案

    或者,如果我们处于从右到左的语言环境中,那么侵入性较小的解决方案是继承 UIPageViewController 并简单地翻转动画方向。

    private extension UIPageViewController.NavigationDirection {
        var flipped: Self {
            switch self {
            case .forward:
                return .reverse
            case .reverse:
                return .forward
            @unknown default:
                return .reverse
            }
        }
    }
    
    class LocalizedPageViewController: UIPageViewController {
        override func setViewControllers(
            _ viewControllers: [UIViewController]?,
            direction: UIPageViewController.NavigationDirection,
            animated: Bool,
            completion: ((Bool) -> Void)? = nil
        ) {
            let isRTL = view.effectiveUserInterfaceLayoutDirection == .rightToLeft
            let direction = isRTL ? direction.flipped : direction
            super.setViewControllers(viewControllers, direction: direction, animated: animated, completion: completion)
        }
    }
    

    那么我只需要将UIPageViewController 的初始化更改为LocalizedPageViewController,无需其他更改!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-27
      • 1970-01-01
      相关资源
      最近更新 更多