UIPageViewController:从右到左的语言反转 .scroll 动画方向
Posted
技术标签:
【中文标题】UIPageViewController:从右到左的语言反转 .scroll 动画方向【英文标题】:UIPageViewController: Reverse .scroll animation direction for right-to-left languages 【发布时间】: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
)
nextTapped
的 inDirection
参数为 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
手动反转以支持从右到左(我试图避免的解决方案)。
排除了ViewModel
和DummyViewController
,因为它们没有改变。
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
【问题讨论】:
【参考方案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
,无需其他更改!
【讨论】:
以上是关于UIPageViewController:从右到左的语言反转 .scroll 动画方向的主要内容,如果未能解决你的问题,请参考以下文章