Learn2Code
Learn2Code

Reputation: 2280

SwiftUI - Autoscroll horizontal scrollview to show full text label

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

Answers (1)

Shaybc
Shaybc

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)
    }
}

Optional:

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:

aheze spot-on answer

Upvotes: 1

Related Questions