Reputation: 2280
I have the following code which works perfectly. But the one issue I can not solve if that when on the of the Titles
is particularly visible in the scrollview and the user click on the portion of the text that is visible, not only do I want to have the title selected I would like for the scollview to "auto scroll" so that the full title is displayed in the scrollview vs only the partial text.
import SwiftUI
struct CustomSegmentedPickerView: View {
@State private var selectedIndex = 0
private var titles = ["Round Trip", "One Way", "Multi-City", "Other"]
private var colors = [Color.red, Color.green, Color.blue, Color.yellow]
@State private var frames = Array<CGRect>(repeating: .zero, count: 4)
var body: some View {
VStack {
ScrollView(.horizontal, showsIndicators: false) {
ZStack {
HStack(spacing: 10) {
ForEach(self.titles.indices, id: \.self) { index in
Button(action: { self.selectedIndex = index }) {
Text(self.titles[index])
}.padding(EdgeInsets(top: 16, leading: 20, bottom: 16, trailing: 20)).background(
GeometryReader { geo in
Color.clear.onAppear { self.setFrame(index: index, frame: geo.frame(in: .global)) }
})
}
}
.background(
Capsule().fill(self.colors[self.selectedIndex].opacity(0.4))
.frame(width: self.frames[self.selectedIndex].width,
height: self.frames[self.selectedIndex].height, alignment: .topLeading)
.offset(x: self.frames[self.selectedIndex].minX - self.frames[0].minX)
, alignment: .leading
)
}
.animation(.default)
.background(Capsule().stroke(Color.gray, lineWidth: 3))
Picker(selection: self.$selectedIndex, label: Text("What is your favorite color?")) {
ForEach(0..<self.titles.count) { index in
Text(self.titles[index]).tag(index)
}
}.pickerStyle(SegmentedPickerStyle())
Text("Value: \(self.titles[self.selectedIndex])")
Spacer()
}
}
}
func setFrame(index: Int, frame: CGRect) {
self.frames[index] = frame
}
}
struct CustomSegmentedPickerView_Previews: PreviewProvider {
static var previews: some View {
CustomSegmentedPickerView()
}
}
Upvotes: 2
Views: 1442
Reputation: 3147
try this:
struct ScrollText: View
{
@State var scrollText: Bool = false
var body: some View
{
let textWidth: CGFloat = 460
let titleWidth: CGFloat = 240.0
let titleHeight: CGFloat = 70.0
HStack
{
ScrollView(.horizontal)
{
Text("13. This is my very long text title for a tv show")
.frame(minWidth: titleWidth, minHeight: titleHeight, alignment: .center)
.offset(x: (titleWidth < textWidth) ? (scrollText ? (textWidth * -1) - (titleWidth / 2) : titleWidth ) : 0, y: 0)
.animation(Animation.linear(duration: 10).repeatForever(autoreverses: false), value: scrollText)
.onAppear {
self.scrollText.toggle()
}
}
}
.frame(maxWidth: titleWidth, alignment: .center)
}
}
if you want to calculate the text width (and not use a constant) then you can use a GeometryReader (that reads the actual dimensions of rendered objects at real time)
or you can use UIKit to calculate the width (i use this method and it is very reliant and can achieve calculations before rendering has occurred)
add this extension to your code:
// String+sizeUsingFont.swift
import Foundation
import UIKit
import SwiftUI
extension String
{
func sizeUsingFont(fontSize: CGFloat, weight: Font.Weight) -> CGSize
{
var uiFontWeight = UIFont.Weight.regular
switch weight {
case Font.Weight.heavy:
uiFontWeight = UIFont.Weight.heavy
case Font.Weight.bold:
uiFontWeight = UIFont.Weight.bold
case Font.Weight.light:
uiFontWeight = UIFont.Weight.light
case Font.Weight.medium:
uiFontWeight = UIFont.Weight.medium
case Font.Weight.semibold:
uiFontWeight = UIFont.Weight.semibold
case Font.Weight.thin:
uiFontWeight = UIFont.Weight.thin
case Font.Weight.ultraLight:
uiFontWeight = UIFont.Weight.ultraLight
case Font.Weight.black:
uiFontWeight = UIFont.Weight.black
default:
uiFontWeight = UIFont.Weight.regular
}
let font = UIFont.systemFont(ofSize: fontSize, weight: uiFontWeight)
let fontAttributes = [NSAttributedString.Key.font: font]
return self.size(withAttributes: fontAttributes)
}
}
and use it like this:
let textWidth: CGFloat = "13. This is my very long text title for a tv show".sizeUsingFont(fontSize: 24, weight: Font.Weight.regular).width
of course put the text in a var and change the font size and weight to meet your needs
also if you are about to use this solution inside a button's label, i suggest putting the onAppear()
code inside async call, see this answer:
Upvotes: 1