Как реализовать кнопку стиля «подробнее» в конце текста в SwiftUI

#swift #swiftui #xcode11 #swiftui-text #swiftui-button

#swiftui

Вопрос:

У меня очень длинный текст, и я хочу показать всего 3 строки с кнопкой «больше«, как на картинке, а также с кнопкой «меньше», когда текст расширяется. Есть идеи, как это сделать с помощью SwiftUI?

введите описание изображения здесь

 var body: some View{
    VStack{
        Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
    }
}
  

Комментарии:

1. Смотрите обновление, пожалуйста

2. Рассматривали ли вы возможность использования модификатора Button в .overlay представлении, который применяется к вашему Text представлению?

3. Не могли бы вы использовать код, чтобы показать мне?

Ответ №1:

Этот ответ немного взламывает, потому что он не усекает фактическую строку и не применяет суффикс «…», что, по моему скромному мнению, было бы лучшим инженерным решением. Для этого программисту потребуется определить длину строки, которая умещается в трех строках, удалить последние два слова (чтобы разрешить кнопку «Больше / меньше») и применить суффикс «…».

Это решение ограничивает количество отображаемых строк и буквально закрывает конец третьей строки белым фоном и кнопкой. Но это может подойти для вашего случая…

 @State private var isExpanded: Bool = false

var body: some View{
    
    VStack{

        Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
            .lineLimit(isExpanded ? nil : 3)
            .overlay(
                GeometryReader { proxy in
                    Button(action: {
                        isExpanded.toggle()
                    }) {
                        Text(isExpanded ? "Less" : "More")
                            .font(.caption).bold()
                            .padding(.leading, 8.0)
                            .padding(.top, 4.0)
                            .background(Color.white)
                    }
                    .frame(width: proxy.size.width, height: proxy.size.height, alignment: .bottomTrailing)
                }
            )
    }
}
  

Вы можете узнать, как это сделать, следуя инструкциям Apple «Знакомство с SwiftUI«. В частности, в руководстве «Создание приложения для macOS», «Раздел 9 Создание подробного представления«.

Комментарии:

1. Спасибо за быстрый ответ. На самом деле это сработало, но если я сделаю текст немного длиннее, он не отобразит целостный текст

2. Когда я добавляю FixedSize к тексту («»), весь текст отображается, но не прокручивается. Кажется, что что-то блокирует прокрутку вверх или вниз

3. В зависимости от используемого вами программного обеспечения могут возникнуть проблемы при попытке использовать .fixedSize модификатор — он работает не так, как ожидалось. Сообщите Apple об этом, отправив отзыв.

Ответ №2:

Я также пытался достичь тех же результатов, что и вы. Также потратил часы на поиск решения, когда оно все время было передо мной …!

Решение состоит в том, чтобы использовать ZStack вместе с ScrollView и GeometaryReader одновременно…

 struct CollapsableTextView: View {
    let lineLimit: Int
    
    @State private var expanded: Bool = false
    @State private var showViewButton: Bool = false
    private var text: String
    
    init(_ text: String, lineLimit: Int) {
        self.text = text
        self.lineLimit = lineLimit
        
    }
    
    private var moreLessText: String {
        if showViewButton {
            return expanded ? "View Less" : "View More"
            
        } else {
            return ""
            
        }
    }
    
    var body: some View {
        VStack(alignment: .leading) {
            ZStack {
                Text(text)
                    .font(.body)
                    .lineLimit(expanded ? nil : lineLimit)
                
                ScrollView(.vertical) {
                    Text(text)
                        .font(.body)
                        .background(
                            GeometryReader { proxy in
                                Color.clear
                                    .onAppear {
                                        showViewButton = proxy.size.height > CGFloat(22 * lineLimit)
                                    }
                                    .onChange(of: text) { _ in
                                        showViewButton = proxy.size.height > CGFloat(22 * lineLimit)
                                    }
                            }
                        )
                    
                }
                .opacity(0.0)
                .disabled(true)
                .frame(height: 0.0)
            }
            
            Button(action: {
                withAnimation {
                    expanded.toggle()
                }
            }, label: {
                Text(moreLessText)
                    .font(.body)
                    .foregroundColor(.orange)
            })
            .opacity(showViewButton ? 1.0 : 0.0)
            .disabled(!showViewButton)
            .frame(height: showViewButton ? nil : 0.0)
            
        }
    }
}

struct CollapsableTextView_Previews: PreviewProvider {
    static var previews: some View {
        CollapsableTextView("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.", lineLimit: 3)
    }
    
}
  

Наиболее важная часть этого кода CGFloat(22 * lineLimit) здесь 22 — это высота отдельной строки с указанным используемым шрифтом. Возможно, вам придется изменить высоту (в данном случае 22) в зависимости от вашего шрифта…

В остальном все довольно просто. Я надеюсь, что это может помочь …!

Ответ №3:

Вы можете прочитать эту статью Medium

 struct ExpandableText: View {
    
    @State private var expanded: Bool = false
    @State private var truncated: Bool = false
    @State private var shrinkText: String
    
    private var text: String
    let font: UIFont
    let lineLimit: Int
    
    init(_ text: String, lineLimit: Int, font: UIFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)) {
        self.text = text
        _shrinkText =  State(wrappedValue: text)
        self.lineLimit = lineLimit
        self.font = font
    }
    
    var body: some View {
        ZStack(alignment: .bottomLeading) {
            Group {
                Text(self.expanded ? text : shrinkText)   Text(moreLessText)
                    .bold()
                    .foregroundColor(.black)
            }
            .animation(.easeInOut(duration: 1.0), value: false)
            .lineLimit(expanded ? nil : lineLimit)
            .background(
                // Render the limited text and measure its size
                Text(text)
                    .lineLimit(lineLimit)
                    .background(GeometryReader { visibleTextGeometry in
                        Color.clear.onAppear() {
                            let size = CGSize(width: visibleTextGeometry.size.width, height: .greatestFiniteMagnitude)
                            let attributes:[NSAttributedString.Key:Any] = [NSAttributedString.Key.font: font]

                            ///Binary search until mid == low amp;amp; mid == high
                            var low  = 0
                            var heigh = shrinkText.count
                            var mid = heigh ///start from top so that if text contain we does not need to loop
                            ///
                            while ((heigh - low) > 1) {
                                let attributedText = NSAttributedString(string: shrinkText   moreLessText, attributes: attributes)
                                let boundingRect = attributedText.boundingRect(with: size, options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil)
                                if boundingRect.size.height > visibleTextGeometry.size.height {
                                    truncated = true
                                    heigh = mid
                                    mid = (heigh   low)/2
                                    
                                } else {
                                    if mid == text.count {
                                        break
                                    } else {
                                        low = mid
                                        mid = (low   heigh)/2
                                    }
                                }
                                shrinkText = String(text.prefix(mid))
                            }
                            
                            if truncated {
                                shrinkText = String(shrinkText.prefix(shrinkText.count - 2))  //-2 extra as highlighted text is bold
                            }
                        }
                    })
                    .hidden() // Hide the background
            )
            .font(Font(font)) ///set default font
            ///
            if truncated {
                Button(action: {
                    expanded.toggle()
                }, label: {
                    HStack { //taking tap on only last line, As it is not possible to get 'see more' location
                        Spacer()
                        Text("")
                    }.opacity(0)
                })
            }
        }
    }
    
    private var moreLessText: String {
        if !truncated {
            return ""
        } else {
            return self.expanded ? " read less" : " ... read more"
        }
    }
    
}
  

И используйте этот расширяемый текст в вашем представлении, как показано ниже

 ExpandableText("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut laborum", lineLimit: 6)