SwiftUI | How to make 3D carousel view

Enebin
4 min readOct 6, 2021

--

The PANTONE Plus Plastic Standard Chips Collection (PSC-PS1755) | Pantone

While I looking for some design references, I happened to see an image of Pantone’s plastic color chip sets commonly seen in design studios.

For those who are hasty, here’s the final view.

Then, let’s dive in!

How to rotate?

This can be implemented by using rotation3DEffect

count is fixed to 5 in this case

To make it simple, let’s begin with the empty square. As you can see from the above GIF, rotation3DEffect make a view rotate around x, y, z axis. You can check details in the official document.

So what we have to do first is making our view to rotate around y-axis like real Pantone color sets. In addition, to make our view act more naturally, we should think about other options like anchorand perspective.

anchor should be placed in the position where the central axis is desired and perspectiveis used to adjust perspective literally. I set anchorto UnitPoint(x: -2, y: -1.5) because people are used to the top-down view. Okay. Now it seems the only thing we should do is just replacing Rectangle to a card shape.

However, before we jump into it, there is an unsolved problem we should deal with. That is, because of the feature of ForEach, zIndex of cards at the back is higher than card at the front, and so the card at the front is blocked by them. We will look into this issue in more detail at next part. In conclusion, this requires adjusting the zIndex of each card.

Deal with zIndex

Left: Malfunctioning view / Right: After it fixed

Simply put, this problem can be solved when the front card has a larger zIndex than the back card. For this, I came up with some concepts: relativeIndex and zIndexPreset. relativeIndex is the index of cards newly calculated based on 0 degrees, that is, the rightmost part of carousel. In other words, It can be said I created kind of new angular coordinate system. Anyway, the code is below.

zIndexPreset is an array which has decreasing numbers. For example, a zIndexPreset of length 5 will be an array like [5, 4, 3, 2, 1]. Of course, actual element in there will be little bit different because zIndex has to have its input as Double between 0 and 1. I will use zIndexPreset to define the zIndex of each card in the way like zIndexPreset[relativeIndex].

However, if relativeIndex is used without any correction, the relativeIndex of the card in front of 0 degrees becomes larger than the cards behind, which means they will be blocked by the back card again. To prevent this, I created a variable called correctdRelativeIndex.

Actually, before doing this, we have to do some math figuring out which card is located in the 0 degree based on the current angle.

Yeah, it’s quite complicated. I tried my best, but I believe there would be easier and better solution. If you have any better ideas, It would be appreciated if you let me know...

Add some details

If you add offset you can make your card pop out.

chipShapes[index]
.offset(y: currentCard == index ? 100 : 0)

Basic things are all done. You can now add some design elements like ‘card case’, ‘shadow’ to your liking. I added spanning RGB color and using VStack to stack them up.

Actually, the color codes written on the chips are ‘RGB’ code, not the ‘Pantone’ code. Except for that, it works really fine anyway.🤣

--

--