74

this is a pretty straightforward question, but I haven't been able to find a definitive answer to it on SO (if I missed it, please correct me).

Basically, my question is: Is it possible to align UICollectionView row contents from right to left instead of from left to right?

In my research I've seen answers suggesting subclassing UICollectionViewFlowLayout, but I haven't been able to find an example where one was created for right-alignment.

My goal is to have 2 collection views set up like this:

Example

Any help is greatly appreciated!

rebello95
  • 8,486
  • 5
  • 44
  • 65
  • 1
    i haven't use this but maybe this [link](https://www.cocoacontrols.com/controls/uicollectionviewrightalignedlayout) will helo you. – ChintaN -Maddy- Ramani Sep 01 '14 at 04:10
  • if you ant to follow all the collectionvews/scrollview RT try to follow this link https://medium.com/if-let-swift-programming/working-with-localization-in-swift-4a87f0d393a4 – Johnykutty Sep 06 '17 at 07:57

16 Answers16

110

Without doing any Xtransform to the collection view, simply forced RTL:

YourCollectionView.semanticContentAttribute = UISemanticContentAttribute.forceRightToLeft
Ronan Boiteau
  • 9,608
  • 6
  • 34
  • 56
Tawfik Bouabid
  • 1,282
  • 2
  • 9
  • 16
  • this is the solution, working great with texts. the previous solutions not works good with texts – Ofir Malachi May 22 '18 at 12:30
  • This technically should be the correct answer. However - it does appear to stop collection view batch update animations from working (where as the transform hack does not). – So Over It Jul 17 '19 at 06:03
92

You can get similar result by performing a transform on the collection view and reverse the flip on its content:

First when creating the UICollectionView I performed a horizontal flip on it:

[collectionView_ setTransform:CGAffineTransformMakeScale(-1, 1)];

Then subclass UICollectionViewCell and in here do the same horizontal flip on its contentView:

[self.contentView setTransform:CGAffineTransformMakeScale(-1, 1)];
LK__
  • 6,515
  • 5
  • 34
  • 53
  • 3
    If you have any subviews in your cell, then you have transform it as well. For e.g. `[self.lblTitle setTransform:CGAffineTransformMakeScale(-1, 1)];` – Hemang May 17 '16 at 13:34
  • @Hemang you sure you flipped the collectionviewcell? This should be sufficient for the context presuming all are sub ores of this – LK__ May 17 '16 at 15:36
  • That's a good trick. Unfortunately the scroll/indicator is inverted too. Another trick is to hide it and scroll to the last item when you reload the data. – Marie Dm Nov 23 '17 at 12:24
  • Better answer (with less votes as of now) below: https://stackoverflow.com/a/60389015/826946. See the note I added there which sends you to another answer with example code. – Andy Weinstein Jun 16 '21 at 07:28
35

In addition to Tawfik's answer:

You can also set UICollectionView's Semantic property via Interface Builder:

Storyboard

More about this property: in this question

Community
  • 1
  • 1
medvedNick
  • 4,512
  • 4
  • 32
  • 50
28

you can use this since iOS 11:

extension UICollectionViewFlowLayout {

    open override var flipsHorizontallyInOppositeLayoutDirection: Bool {
        return true
    }

}
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
Marwan Alqadi
  • 795
  • 8
  • 14
  • You may need to subclass UICollectionViewFlowLayout. Using this property is a better idea than misusing RTL support which is intended for languages that read right-to-left. – Ilias Karim Dec 26 '18 at 03:46
27

By changing both flipsHorizontallyInOppositeLayoutDirection and developmentLayoutDirection, I was able to achieve a full screen RTL scroll where the first item was all the way to the right, and to reach the last cell, user will need to scroll to the left.

Like so:

class RTLCollectionViewFlowLayout: UICollectionViewFlowLayout {

    override var flipsHorizontallyInOppositeLayoutDirection: Bool {
        return true
    }

    override var developmentLayoutDirection: UIUserInterfaceLayoutDirection {
        return UIUserInterfaceLayoutDirection.rightToLeft
    }
}
cohen72
  • 2,830
  • 29
  • 44
22

For anybody trying to achieve Right to Left layout of UICollectionView in Swift

//in viewDidLoad
    YourCollectionView.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)

//in cellForItemAtIndexPath
    cell.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
James Kuang
  • 10,710
  • 4
  • 28
  • 38
Saqib Omer
  • 5,387
  • 7
  • 50
  • 71
21

As of iOS 9, Collection Views support RTL according to this WWDC video. So it's no longer necessary to create an RTL flow layout (unless you're already using a custom layout).

Select: Edit Scheme... > Options > Run > Application Language > Right to Left Pseudolanguage

enter image description here

When you build to Simulator, text will be right-aligned, and your Collection View will be ordered from Right to Left.

enter image description here

There's a problem though. When contentOffset.x == 0, the Collection View scroll position is at the Left edge (wrong) instead of the Right edge (correct). See this stack article for details.

One workaround is to simply scroll the First item to the .Left (There's a gotcha -- .Left is actually on the Right, or Leading edge):

override func viewDidAppear(animated: Bool) {
    if collectionView?.numberOfItemsInSection(0) > 0  {
        let indexPath = NSIndexPath(forItem: 0, inSection: 0)
        collectionView?.scrollToItemAtIndexPath(indexPath, atScrollPosition: .Left, animated: false)
    }
}

In my test project, my Collection View was nested inside a Table View Cell, so I didn't have access to viewDidAppear(). So instead, I ended up hooking into drawRect():

class CategoryRow : UITableViewCell {
    @IBOutlet weak var collectionView: UICollectionView!

    override func drawRect(rect: CGRect) {
        super.drawRect(rect)
        scrollToBeginning()
    }

    override func prepareForReuse() {
        scrollToBeginning()
    }

    func scrollToBeginning() {
        guard collectionView.numberOfItems(inSection: 0) > 0 else { return }
        let indexPath = IndexPath(item: 0, section: 0)
        collectionView.scrollToItem(at: indexPath, at: .left, animated: false)
    }
}

To see this in action, check out the RTL branch on this git repo. And for context, see this blog post and its comments.

enter image description here

mt81
  • 3,288
  • 1
  • 26
  • 35
Robert Chen
  • 5,179
  • 3
  • 34
  • 21
  • Thanks for this.. to avoid headaches, I just scroll to an arbitrarily large number, like 100000, and it moves to right. Your way is better, tho. – Mazyod Jun 30 '16 at 21:54
  • 1
    Down-voted this in favor of moving the UISemanticContentAttribute response up. Very thorough though! – Doug Mead Jun 16 '17 at 17:59
  • Saved me!! I just found this issue at the night of deployment and surprisingly I too had the collectionview inside the tableView cell. Thanks for this answer. – Shivam Pokhriyal Jul 17 '19 at 13:48
  • @DougMead why would using UISemanticContentAttribute is better? its not better at all. the uicollectionview already support the rtl. many reported performance issues when keep changing the value of UISemanticContentAttribute. that can happen when u support both rtl and ltr. I would vote for flipsHorizontallyInOppositeLayoutDirection to be the best solution. in there u can check ur app state. and to make an exceptions for some uicollectionviews. subclassing UICollectionViewFlowLayout is the solution. – hasan Jul 18 '20 at 13:04
  • UISemanticContentAttribute is not the best solution. it says force for some reason. its appears that apple didn't create this to be used this way. – hasan Jul 18 '20 at 13:04
11
extension UICollectionViewFlowLayout {

    open override var flipsHorizontallyInOppositeLayoutDirection: Bool {
        return true
    }

}
urvashi bhagat
  • 1,123
  • 12
  • 15
7

For anyone who has the same question:

I ended up using the UICollectionViewRightAlignedLayout library that @chinttu-roxen-ramani recommended. You can either set it with code:

self.collectionView.collectionViewLayout = [[UICollectionViewRightAlignedLayout alloc] init];

or through interface builder:

Through interface builder

I ended up making a couple modifications to the library, but overall it works great.

rebello95
  • 8,486
  • 5
  • 44
  • 65
  • hi, i need to use the library too with swift. so could you please tell me about the modification that u applied on the library? thanks alot. – Rawan Jun 11 '15 at 16:25
  • I tried this library in iOS 9, and it produces warnings (which someone also reported on the github project for it). Also it doesn't respect cell dimensions specified in Interface Builder. – siannopollo Oct 03 '15 at 15:05
7

Adding below extension worked for me

extension UICollectionViewFlowLayout {
    open override var flipsHorizontallyInOppositeLayoutDirection: Bool {
        return true  //RETURN true if collection view needs to enable RTL
    }
}
Krishna Kirana
  • 438
  • 4
  • 10
4

For iOS 9+

  1. Reverse your collectionView in viewDidLoad() with this :

    myCollectionView.transform = CGAffineTransform(scaleX: -1, y: 1)

  2. Reverse-Back your cell (because all things are mirrored) in cellForItemAt with this :

    cell.transform = CGAffineTransform(scaleX: -1, y: 1)

Now content is on right side and scroll starts from right.

Sabrina
  • 2,531
  • 1
  • 32
  • 30
2

UICollectionView already support rtl direction if UICollectionViewFlowLayout is not dynamic.

Changing Estimate size to None as the image shows automatically changed the direction of the CollectionView.

enter image description here

One more issue is to scroll to the end of the collection view after reloading data.

extension UICollectionView {
    
    func scrollToEndIfArabic() {
        if Language.shared.isArabic() {
            DispatchQueue.main.async {
                self.contentOffset
                    = CGPoint(x: self.contentSize.width
                        - self.frame.width
                        + self.contentInset.right, y: 0)
            }
        }
    }
}
hasan
  • 23,815
  • 10
  • 63
  • 101
1

Non of the above answers worked for me. The main reason was that most of them are not complete. However, I found this solution from this link by AlexSerdobintsev.

In AppDelegate.cs. First, you have to import the following function

[DllImport(ObjCRuntime.Constants.ObjectiveCLibrary, EntryPoint = "objc_msgSend")]
internal extern static IntPtr IntPtr_objc_msgSend(IntPtr receiver, IntPtr selector, UISemanticContentAttribute arg1);

Then call the function inside FinishedLaunching

        var selector = new ObjCRuntime.Selector("setSemanticContentAttribute:");
        IntPtr_objc_msgSend(UIView.Appearance.Handle, selector.Handle, UISemanticContentAttribute.ForceRightToLeft);

voila! we are done. Here is the file after applying the changes:

    [Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
    [DllImport(ObjCRuntime.Constants.ObjectiveCLibrary, EntryPoint = "objc_msgSend")]
    internal extern static IntPtr IntPtr_objc_msgSend(IntPtr receiver, IntPtr selector, UISemanticContentAttribute arg1);


    //
    // This method is invoked when the application has loaded and is ready to run. In this 
    // method you should instantiate the window, load the UI into it and then make the window
    // visible.
    //
    // You have 17 seconds to return from this method, or iOS will terminate your application.
    //
    public override bool FinishedLaunching(UIApplication app, NSDictionary options)
    {
        global::Xamarin.Forms.Forms.SetFlags("Shell_Experimental", "Visual_Experimental", "CollectionView_Experimental", "FastRenderers_Experimental");
        global::Xamarin.Forms.Forms.Init();
        LoadApplication(new App());

        ...

        var selector = new ObjCRuntime.Selector("setSemanticContentAttribute:");
        IntPtr_objc_msgSend(UIView.Appearance.Handle, selector.Handle, UISemanticContentAttribute.ForceRightToLeft);

        return base.FinishedLaunching(app, options);
    }
}
Ashi
  • 806
  • 1
  • 11
  • 22
1

From what I can tell, all of these answers want to fill the collection view from right to left. For example, if you labeled cells 1, 2, 3, then they would appear in the order 3, 2, 1 in the collection view. In my case I wanted the cells to appear in the order 1, 2, 3 (like text right alignment when the line is not full). In order to do this I created a simple UICollectionViewFlow layout.

import UIKit

class RightAlignFlowLayout: UICollectionViewFlowLayout {

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
    {
        guard let attrsArr = super.layoutAttributesForElements(in: rect) else {
            return nil
        }
        guard let collectionView = self.collectionView else { return attrsArr }
        if self.collectionViewContentSize.width > collectionView.bounds.width {
            return attrsArr
        }

        let remainingSpace = collectionView.bounds.width - self.collectionViewContentSize.width

        for attr in attrsArr {
            attr.frame.origin.x += remainingSpace
        }

        return attrsArr
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard let attrs = super.layoutAttributesForItem(at: indexPath) else { return nil }
        guard let collectionView = self.collectionView else { return attrs }
        if self.collectionViewContentSize.width > collectionView.bounds.width {
            return attrs
        }

        let remainingSpace = collectionView.bounds.width - self.collectionViewContentSize.width
        attrs.frame.origin.x += remainingSpace
        return attrs
    }

}
SamB
  • 2,621
  • 4
  • 34
  • 39
0

None of the above answers working for me. So finally I fixed my issue with this. I am using RTL for 2 languages(Arabic, French) Here is my solution

Use this line for transferring collection view left to right

let LangCheck = PPLocalization.sharedInstance.getLanguage(forAPI: true)
if(LangCheck == "ar"){
   CollectionView.transform = CGAffineTransform(scaleX: -1, y: 1);
}

Use this line to make fit your label inside your collection view cell

let LangCheck = PPLocalization.sharedInstance.getLanguage(forAPI: true)
  if(LangCheck == "ar"){
     cell.lbCategoryName.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
  }
Md. Shofiulla
  • 2,135
  • 1
  • 13
  • 19
-1

Needs to do 2 things:

1-

extension UICollectionViewFlowLayout {

    open override var flipsHorizontallyInOppositeLayoutDirection: Bool {
        return true
    }

}

2-

self.categoryCollectionView.semanticContentAttribute = UISemanticContentAttribute.forceRightToLeft
HafizAnser
  • 553
  • 6
  • 10
  • Never override anything in an extension. It's undefined behavior. It might work. It might not work. What it multiple parts of the app create extensions that override the same method/property? Which one wins? Again, it's undefined and should never be done. – HangarRash Apr 28 '23 at 16:24