Skip to content

Instantly share code, notes, and snippets.

@liudasbar
Created September 27, 2021 19:00
Show Gist options
  • Save liudasbar/ff0c74dbd651a0b7239b4d9931a97760 to your computer and use it in GitHub Desktop.
Save liudasbar/ff0c74dbd651a0b7239b4d9931a97760 to your computer and use it in GitHub Desktop.
SwiftUI Custom Segmented Control with animation
struct CustomSegmentedControl: View {
@Binding var arrayOfItems: [Item]
@Binding var selection: Item
@State private var frames: [CGRect] = Array(repeating: .zero, count: 3)
var body: some View {
ScrollView(.horizontal, showsIndicators: false, content: {
HStack(spacing: 0) {
ForEach(arrayOfItems.indices, id: \.self) { index in
Button(
arrayOfItems[index],
action: {
selection = arrayOfItems[index]
})
.font(.body)
.foregroundColor(selection == arrayOfItems[index] ? Color.black : Color.gray)
.padding([.leading, .trailing], 16)
.padding([.top, .bottom], 5)
.buttonStyle(PlainButtonStyle())
.background(
GeometryReader { geo in
Color.clear.onAppear { self.setFrame(index: index, frame: geo.frame(in: .global)) }
}
)
}
}
.padding([.leading, .trailing], 16)
.padding([.top, .bottom], 30)
.background(
Rectangle()
.fill(Color.white)
.cornerRadius(8)
.frame(
width: self.frames[selectionIndex()].width,
height: 30,
alignment: .center
)
.offset(x: self.frames[selectionIndex()].minX - self.frames[0].minX + 16),
alignment: .leading
)
.animation(.easeInOut(duration: 0.2))
})
}
private func selectionIndex() -> Int {
for (index, item) in arrayOfItems.enumerated() where item == selection {
return index
}
return 0
}
private func setFrame(index: Int, frame: CGRect) {
self.frames[index] = frame
}
}
@liudasbar
Copy link
Author

liudasbar commented Sep 27, 2021

In case any visual issues appear, make sizes the constants.

  • Print frames array.
  • Write down the x, y, width and height of each item.
  • Set Rectangle's frame width to constant sizes (that you wrote down in the previous step). Use switch.
  • Set Rectangle's x offset to constant sizes (that you wrote down in the previous step). Again, use switch.

Example:

   Rectangle()
        .fill(Color.white)
        .cornerRadius(8)
        .frame(
            width: rectangleWidth(),
            height: 30,
            alignment: .center
        )
        .offset(x: rectangleXOffset())


    private func rectangleWidth() -> CGFloat {
        switch selection {
        case .itemOne:
            return 134.66
        case .itemTwo:
            return 189
        case .itemThree:
            return 57.33
        }
    }

    private func rectangleXOffset() -> CGFloat {
        switch selection {
        case . itemOne:
            return 16
        case . itemTwo:
            return 150.66
        case .itemThree:
            return 339.66
        }
    }

@liudasbar
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment