From fbfe5e914812f8efe98a724005efb71b6d03c1ee Mon Sep 17 00:00:00 2001 From: Adrian Schoenig Date: Fri, 7 Jan 2022 09:20:49 +1100 Subject: [PATCH] RTL-support (#11) --- .../TGCardViewController.swift | 16 ++- .../cards/TGPageCardView.swift | 124 +++++++++++++----- 2 files changed, 104 insertions(+), 36 deletions(-) diff --git a/Sources/TGCardViewController/TGCardViewController.swift b/Sources/TGCardViewController/TGCardViewController.swift index a2254e7..ece3c83 100644 --- a/Sources/TGCardViewController/TGCardViewController.swift +++ b/Sources/TGCardViewController/TGCardViewController.swift @@ -372,7 +372,7 @@ open class TGCardViewController: UIViewController { let edgePanner = UIScreenEdgePanGestureRecognizer() edgePanner.addTarget(self, action: #selector(popMaybe)) edgePanner.isEnabled = mode == .floating - edgePanner.edges = .left + edgePanner.edges = traitCollection.layoutDirection == .leftToRight ? .left : .right view.addGestureRecognizer(edgePanner) self.edgePanner = edgePanner } @@ -1144,7 +1144,12 @@ extension TGCardViewController { // centres the current location, etc. var mapInsets = UIEdgeInsets.zero if cardIsNextToMap(in: traitCollection) { - mapInsets.left = cardWrapperShadow.isHidden ? 0 : (traitCollection.horizontalSizeClass == .regular ? 360 : view.frame.width * 0.38) // same as in storyboard + let cardWidth = cardWrapperShadow.isHidden ? 0 : (traitCollection.horizontalSizeClass == .regular ? 360 : view.frame.width * 0.38) // same as in storyboard + if traitCollection.layoutDirection == .rightToLeft { + mapInsets.right = cardWidth + } else { + mapInsets.left = cardWidth + } } else { // Our view has the full height, including what's below safe area insets. // The maximum interactive area is that without the bottom and anything @@ -1163,7 +1168,12 @@ extension TGCardViewController { mapInsets.top = headerView.frame.maxY } if !bottomFloatingView.arrangedSubviews.isEmpty { - mapInsets.right = bottomFloatingViewWrapper.bounds.width + let floatingWidth = bottomFloatingViewWrapper.bounds.width + if traitCollection.layoutDirection == .rightToLeft { + mapInsets.left = floatingWidth + } else { + mapInsets.right = floatingWidth + } } return mapInsets } diff --git a/Sources/TGCardViewController/cards/TGPageCardView.swift b/Sources/TGCardViewController/cards/TGPageCardView.swift index 42222f2..7454a38 100644 --- a/Sources/TGCardViewController/cards/TGPageCardView.swift +++ b/Sources/TGCardViewController/cards/TGPageCardView.swift @@ -8,7 +8,6 @@ import UIKit - protocol TGPageCardViewDelegate: AnyObject { func didChangeCurrentPage(to index: Int, animated: Bool) @@ -17,7 +16,7 @@ protocol TGPageCardViewDelegate: AnyObject { class TGPageCardView: TGCardView { - fileprivate enum Constants { + private enum Constants { static let animationDuration: Double = 0.4 static let spaceBetweenCards: CGFloat = 5 } @@ -28,28 +27,36 @@ class TGPageCardView: TGCardView { @IBOutlet weak var pagerTrailingConstant: NSLayoutConstraint! - fileprivate var lastHorizontalOffset: CGFloat = 0 + private var lastHorizontalOffset: CGFloat = 0 + + private var visiblePageLefty: Int = 0 - fileprivate var visiblePage: Int = 0 + private var visiblePageLogical: Int = 0 weak var delegate: TGPageCardViewDelegate? + /// The card views, ordered as provided, i.e., logically first one first; not by how it appears. var cardViews: [TGCardView] { (contentView.subviews as? [TGCardView]) ?? [] } + private var pageCount: Int { + cardViews.count + } + private func cardView(index: Int) -> TGCardView? { let cardViews = self.cardViews guard index >= 0, index <= cardViews.count else { return nil } return cardViews[index] } + private var topView: UIView? override var preferredView: UIView? { - cardViews.first?.preferredView ?? self + topView ?? self } override var grabHandles: [TGGrabHandleView] { - cardViews.compactMap { $0.grabHandle } + cardViews.compactMap(\.grabHandle) } override var headerHeight: CGFloat { @@ -72,11 +79,8 @@ class TGPageCardView: TGCardView { override var contentScrollView: UIScrollView? { get { - guard currentPage < cardViews.count else { - return nil - } - - return cardViews[currentPage].contentScrollView + let logical = currentLogicalIndex() + return cardView(index: logical)?.contentScrollView } set { assertionFailure("Don't set this on paging a PageCard. Was set to \(String(describing: newValue)).") @@ -89,7 +93,7 @@ class TGPageCardView: TGCardView { // remove corner as we want each card to show its own corners layer.mask = nil - move(to: visiblePage, animated: false) + move(to: visiblePageLogical, animated: false) } // MARK: - New instance @@ -137,13 +141,18 @@ class TGPageCardView: TGCardView { // Page card doesn't always start with page 0. So we keep a reference // to the first page index, which can then be used at a later point. - visiblePage = pageCard.initialPageIndex - + visiblePageLogical = pageCard.initialPageIndex + visiblePageLefty = isRightToLeft + ? contents.count - 1 - pageCard.initialPageIndex + : pageCard.initialPageIndex + // This will be used in both `moveForward` and `moveBackward`, so // it's important to "initailise" this value correctly. - lastHorizontalOffset = CGFloat(pageCard.initialPageIndex) * (frame.width + Constants.spaceBetweenCards) + lastHorizontalOffset = CGFloat(visiblePageLefty) * (frame.width + Constants.spaceBetweenCards) - self.accessibilityElements = [cardView(index: pageCard.initialPageIndex)].compactMap { $0 } + let topMost = cardView(index: visiblePageLogical) + self.accessibilityElements = [topMost].compactMap { $0 } + self.topView = topMost } override func updateDismissButton(show: Bool, isSpringLoaded: Bool) { @@ -208,15 +217,48 @@ class TGPageCardView: TGCardView { // MARK: - Navigation - var currentPage: Int { + private var isRightToLeft: Bool { + traitCollection.layoutDirection == .rightToLeft + } + + private func currentLeftyIndex() -> Int { // Using `floor` here as we're getting fractions here, e.g., on iPhone X in landscape return Int(floor(pager.contentOffset.x) / (floor(frame.width) + Constants.spaceBetweenCards)) } + private func currentLogicalIndex() -> Int { + let lefty = currentLeftyIndex() + return isRightToLeft ? pageCount - lefty - 1 : lefty + } + + var currentPage: Int { + currentLogicalIndex() + } + func moveForward(animated: Bool = true) { + if isRightToLeft { + moveLeft(animated: animated) + } else { + moveRight(animated: animated) + } + } + + func moveBackward(animated: Bool = true) { + if isRightToLeft { + moveRight(animated: animated) + } else { + moveLeft(animated: animated) + } + } + + private func moveRight(animated: Bool) { // Assign `visiblePage` here so that we allow paging there even if VoiceOver is enabled - visiblePage = min(cardViews.count - 1, visiblePage + 1) - self.accessibilityElements = [cardView(index: visiblePage)].compactMap { $0 } + visiblePageLefty = min(cardViews.count - 1, visiblePageLefty + 1) + visiblePageLogical = isRightToLeft ? pageCount - visiblePageLefty - 1 : visiblePageLefty + + let topMost = cardView(index: visiblePageLogical) + self.accessibilityElements = [topMost].compactMap { $0 } + self.topView = topMost // Shift by the entire width of the card view let nextFullWidthHorizontalOffset = pager.contentOffset.x + frame.width + Constants.spaceBetweenCards @@ -241,12 +283,16 @@ class TGPageCardView: TGCardView { lastHorizontalOffset = horizontalOffset } - func moveBackward(animated: Bool = true) { + private func moveLeft(animated: Bool) { // Assign `visiblePage` here so that we allow paging there even if VoiceOver is enabled - visiblePage = max(0, visiblePage - 1) - self.accessibilityElements = [cardView(index: visiblePage)].compactMap { $0 } + visiblePageLefty = max(0, visiblePageLefty - 1) + visiblePageLogical = isRightToLeft ? pageCount - visiblePageLefty - 1 : visiblePageLefty - // See `moveForward()` for comments. + let topMost = cardView(index: visiblePageLogical) + self.accessibilityElements = [topMost].compactMap { $0 } + self.topView = topMost + + // See `moveRight()` for comments. let nextFullWidthHorizontalOffset = pager.contentOffset.x - frame.width - Constants.spaceBetweenCards let nextFullPageHorizontalOffset = lastHorizontalOffset - frame.width - Constants.spaceBetweenCards let horizontalOffset = fmax(nextFullPageHorizontalOffset, nextFullWidthHorizontalOffset) @@ -260,14 +306,22 @@ class TGPageCardView: TGCardView { } func move(to cardIndex: Int, animated: Bool = true) { + let leftIndex = isRightToLeft + ? pageCount - cardIndex - 1 + : cardIndex + // index must fall within the range of available content cards. - guard 0..