ScrollView 및 ForEach 대신 목록 사용

Cyden

사용자가 카드 중 하나를 탭하면 전체 화면으로 확장되는 일부 카드를 표시하는 다음 코드가 있습니다.

import SwiftUI

struct ContentView: View {

    @State private var cards = [
        Card(title: "testA", subtitle: "subtitleA"),
        Card(title: "testB", subtitle: "subtitleB"),
        Card(title: "testC", subtitle: "subtitleC"),
        Card(title: "testD", subtitle: "subtitleD"),
        Card(title: "testE", subtitle: "subtitleE")
    ]

    @State private var showDetails: Bool = false
    @State private var heights = [Int: CGFloat]()

    var body: some View {
        ScrollView {
            VStack {
                if(!cards.isEmpty) {
                    ForEach(self.cards.indices, id: \.self) { index in
                        GeometryReader { reader in
                            CardView(card: self.$cards[index], isDetailed: self.$showDetails)
                            .offset(y: self.cards[index].showDetails ? -reader.frame(in: .global).minY : 0)
                            .fixedSize(horizontal: false, vertical: !self.cards[index].showDetails)
                            .background(GeometryReader {
                                Color.clear
                                    .preference(key: ViewHeightKey.self, value: $0.frame(in: .local).size.height)
                            })
                            .onTapGesture {
                                withAnimation(.spring()) {
                                    self.cards[index].showDetails.toggle()
                                    self.showDetails.toggle()
                                }
                            }
                        }
                        .frame(height: self.cards[index].showDetails ? UIScreen.main.bounds.height : self.heights[index], alignment: .center)
                        .onPreferenceChange(ViewHeightKey.self) { value in
                            self.heights[index] = value
                        }
                    }    
                } else {
                    ActivityIndicator(style: UIActivityIndicatorView.Style.medium).frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height / 2)
                }
            }
        }
        .onAppear() {
            // load data
        }
    }
}


struct CardView : View {
    @Binding var card : Card
    @Binding var isDetailed : Bool

    var body: some View {
        VStack(alignment: .leading){
            ScrollView(showsIndicators: isDetailed && card.showDetails) {
                HStack (alignment: .center){
                    VStack (alignment: .leading){
                        HStack(alignment: .top){
                            Text(card.subtitle).foregroundColor(Color.gray)
                            Spacer()
                        }
                        Text(card.title).fontWeight(Font.Weight.bold).fixedSize(horizontal: false, vertical: true)
                    }
                }
                .padding([.top, .horizontal]).padding(isDetailed && card.showDetails ? [.top] : [] , 34)
            
                Image("placeholder-image").resizable().scaledToFit().frame(width: UIScreen.main.bounds.width - 60).padding(.bottom)
            
                if isDetailed && card.showDetails {
                    Text("Lorem ipsum ... ")
                }
            }
        }
        .background(Color.green)
        .cornerRadius(16)
        .shadow(radius: 12)
        .padding(isDetailed && card.showDetails ? [] : [.top, .horizontal])
        .opacity(isDetailed && card.showDetails ? 1 : (!isDetailed ? 1 : 0))
        .edgesIgnoringSafeArea(.all)
    }
}

struct Card  : Identifiable {
    public var id = UUID()
    public var title: String
    public var subtitle : String
    public var showDetails : Bool = false
}
struct ViewHeightKey: PreferenceKey {
    typealias Value = CGFloat
    static var defaultValue = CGFloat.zero
    static func reduce(value: inout Value, nextValue: () -> Value) {
    value += nextValue()
    }
}

이 코드는이 효과를 생성 합니다 .

이제 더 나은 성능을 위해 지연 로딩으로 인해 ContentView 및 CardView의 Scrollview 및 CardView를 모두 (또는 적어도 하나) 변경하고 싶습니다. 그러나 ContentView에서 ScrollView를 변경하면 애니메이션결함이 발생합니다 . 그리고 CardView에서 List로 변경하면 더 이상 카드가 표시되지 않습니다.

목록을 사용하도록 코드를 어떻게 변경할 수 있는지 아십니까?

무한 드 주마

따라서 의견에서 언급했듯이 깜박임은 Apple 측의 버그입니다. 문제는 오프셋을 변경 한 다음 크기를 조정하면 셀이 겹칠 때 깜박이는 문제가 발생하기 때문에 먼저 크기를 조정 한 다음 오프셋을 변경하려는 것입니다. 페이스트 빈 에서 찾을 수있는 간단한 테스트를 작성했습니다

내 시간 제약으로 인해 2nd / 3rd / etc ... 카드가 애니메이션 순서를 변경하여 맨 위로 점프하는 이상한 스크롤 동작을 해결할 수있었습니다. 오늘 밤이 게시물로 돌아와서 애니메이션이 먼저 발생하는 순서를 수정하여 깜박임을 수정하겠습니다. 그들은 연결되어야합니다.

여기에 내 코드가 있습니다.

init() {
        UITableView.appearance().separatorStyle = .none
        UITableView.appearance().backgroundColor = .clear
        UITableView.appearance().layoutMargins = .zero
        
        UITableViewCell.appearance().backgroundColor = .clear
        UITableViewCell.appearance().layoutMargins = .zero
}
...
        List {
                VStack {
                    if(!cards.isEmpty) {
                        ForEach(self.cards.indices, id: \.self) { index in
                            GeometryReader { reader in
                                CardView(card: self.$cards[index], isDetailed: self.$showDetails)
                                    // 1. Note I switched the order of offset and fixedsize (not that it will make difference in this step but its good practice
                                    // 2. I added animation in between
                                    .fixedSize(horizontal: false, vertical: !self.cards[index].showDetails)
                                    .animation(Animation.spring())
                                    .offset(y: self.cards[index].showDetails ? -reader.frame(in: .global).minY : 0)
                                    .animation(Animation.spring())

                                    .background(GeometryReader {
                                        Color.clear
                                            .preference(key: ViewHeightKey.self, value: $0.frame(in: .local).size.height)
                                    })
                                    .onTapGesture {
                                       // Note I commented this out, it's the source of the bug.
//                                        withAnimation(Animation.spring().delay()) {
                                            self.cards[index].showDetails.toggle()
                                            self.showDetails.toggle()
//                                        }
                                    }
                                .zIndex(self.cards[index].showDetails ? 100 : -1)
                            }
                            .frame(height: self.cards[index].showDetails ? UIScreen.main.bounds.height : self.heights[index], alignment: .center)
                            .onPreferenceChange(ViewHeightKey.self) { value in
                                self.heights[index] = value
                            }
                        }
                    } else {
                        Text("Loading")
                    }
                }
                 // I added this for styling
                .listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
            }
             // I added this for styling
            .listStyle(PlainListStyle())
            .onAppear() {
                // load data
            }

참고 : 그러나 열기와 닫기 사이에 1 ~ 2 초 정도 기다리면 깜박이는 동작을 알 수 없습니다.

참고 2 :이 연결 동작은에서도 볼 수 있지만 App Store애니메이션을 완벽하게 혼합합니다. 주의를 기울이면 약간의 지연이 있습니다.

편집 1 이 편집에서는 이전 솔루션이 애니메이션을 향상시키고 여전히 깜박임 문제가 있음을 지적하고 싶습니다. 안타깝게도 프레젠테이션과 숨기기 사이에 지연이있을 수 있다는 점을 제외하고는 할 수있는 일이 많지 않습니다. 심지어 애플도 그렇게했다. iOS의 App Store에주의를 기울이면 카드 중 하나를 열려고 할 때 "X"를 바로 누를 수 없다는 것을 알게 될 것입니다. 먼저 응답이 없다는 것을 알게 될 것입니다. 발표와 숨길 수있는 사이에 약간의 지연을 추가합니다. 지연은 기본적으로 해결 방법입니다. 또한 카드를 탭하면 즉시 표시되지 않는 것을 알 수 있습니다. 실제로 애니메이션을 실행하는 데 약간의 시간이 걸리며 이는 지연을 추가하는 트릭 일뿐입니다.

따라서 깜박이는 문제를 해결하기 위해 사용자가 열린 카드를 닫거나 닫힌 카드를 다시 열 수 있도록하기 전에 카운트 다운하는 간단한 타이머를 추가 할 수 있습니다. 이 지연은 일부 애니메이션의 지속 시간 일 수 있으므로 사용자가 앱에서 지연을 느끼지 않습니다.

여기에 어떻게 할 수 있는지 보여주는 간단한 코드를 작성했습니다. 그러나 애니메이션 또는 지연의 혼합은 완벽하지 않으며 더 많이 조정할 수 있습니다. 나는 깜박임을 제거하거나 적어도 많이 줄일 수 있음을 보여주고 싶었습니다. 이것보다 훨씬 더 나은 해결책이 있을지 모르지만 그것이 제가 생각 해낸 것입니다. :)

편집 2 : 두 번째 테스트에서 목록에 애니메이션이 없기 때문에이 깜박임이 실제로 발생하고 있음을 지적 할 수있었습니다. 따라서에 애니메이션을 적용하여 빠른 솔루션을 수행 할 수 있습니다 List. 위의 솔루션은 Edit 1:잘 작동하지만 더 이상 기다리지 않고 많은 시행 착오가 필요할 것입니다. 여기에 내 솔루션이 있습니다.

import SwiftUI

struct StackOverflow22: View {
    
    @State private var cards = [
        Card(title: "testA", subtitle: "subtitleA"),
        Card(title: "testB", subtitle: "subtitleB"),
        Card(title: "testC", subtitle: "subtitleC"),
        Card(title: "testD", subtitle: "subtitleD"),
        Card(title: "testE", subtitle: "subtitleE")
    ]
    
    @State private var showDetails: Bool = false
    @State private var heights = [Int: CGFloat]()
    
    init() {
        UITableView.appearance().separatorStyle = .none
        UITableView.appearance().backgroundColor = .clear
        UITableView.appearance().layoutMargins = .zero
        
        UITableViewCell.appearance().backgroundColor = .clear
        UITableViewCell.appearance().layoutMargins = .zero
    }
    
    var body: some View {
        
        List {
            VStack {
                if(!cards.isEmpty) {
                    ForEach(self.cards.indices, id: \.self) { index in
                        GeometryReader { reader in
                            CardView(card: self.$cards[index], isDetailed: self.$showDetails)
                                .fixedSize(horizontal: false, vertical: !self.cards[index].showDetails)
                                .offset(y: self.cards[index].showDetails ? -reader.frame(in: .global).minY : 0)
                                .background(GeometryReader {
                                    Color.clear
                                        .preference(key: ViewHeightKey.self, value: $0.frame(in: .local).size.height)
                                })
                                .onTapGesture {
                                    withAnimation(.spring()) {
                                        self.cards[index].showDetails.toggle()
                                        self.showDetails.toggle()
                                    }
                            }
                        }
                        .frame(height: self.cards[index].showDetails ? UIScreen.main.bounds.height : self.heights[index], alignment: .center)
                            
                        .onPreferenceChange(ViewHeightKey.self) { value in
                            self.heights[index] = value
                        }
                    }
                } else {
                    Text("Loading")
                }
            }
            .animation(Animation.linear)
            .listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
        }
        .listStyle(PlainListStyle())
        .onAppear() {
            // load data
        }
    }
}


struct CardView : View {
    @Binding var card : Card
    @Binding var isDetailed : Bool
    var body: some View {
        VStack(alignment: .leading){
            ScrollView(showsIndicators: isDetailed && card.showDetails) {
                HStack (alignment: .center){
                    VStack (alignment: .leading){
                        HStack(alignment: .top){
                            Text(card.subtitle).foregroundColor(Color.gray)
                            Spacer()
                        }
                        Text(card.title).fontWeight(Font.Weight.bold).fixedSize(horizontal: false, vertical: true)
                    }
                }
                .padding([.top, .horizontal]).padding(isDetailed && card.showDetails ? [.top] : [] , 34)
                
                Image("placeholder-image").resizable().scaledToFit().frame(width: UIScreen.main.bounds.width - 60).padding(.bottom)
                
                if isDetailed && card.showDetails {
                    Text("Lorem ipsum ... ")
                }
            }
        }
        .background(Color.green)
        .cornerRadius(16)
        .shadow(radius: 12)
        .padding(isDetailed && card.showDetails ? [] : [.top, .horizontal])
        .opacity(isDetailed && card.showDetails ? 1 : (!isDetailed ? 1 : 0))
        .edgesIgnoringSafeArea(.all)
    }
}

struct Card  : Identifiable, Hashable {
    public var id = UUID()
    public var title: String
    public var subtitle : String
    public var showDetails : Bool = false
}

struct StackOverflow22_Previews: PreviewProvider {
    static var previews: some View {
        StackOverflow22()
    }
}

struct ViewHeightKey: PreferenceKey {
    typealias Value = CGFloat
    static var defaultValue = CGFloat.zero
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value += nextValue()
    }
}

이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.

침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제

에서 수정
0

몇 마디 만하겠습니다

0리뷰
로그인참여 후 검토

관련 기사

분류에서Dev

foreach 대신 필터 사용

분류에서Dev

사용 목록 .C #의 ForEach

분류에서Dev

중첩 및 다중 foreach 문 대신 LINQ 또는 Lambda를 사용하는 방법

분류에서Dev

대해 forEach, 자바 (8)을 사용하여 목록 목록 (중첩 된 목록)을 생성 스트림

분류에서Dev

for-each 루프 대신 Java 스트림을 사용하여 목록 반복 및 필터링

분류에서Dev

목록 및 단순화 규칙 : # 대신 @를 사용할 때 더 어렵습니까?

분류에서Dev

IntStream 및 대해 forEach를 사용하여 정수의 목록을 작성하는 내 방식의 잘못은 무엇인가?

분류에서Dev

Erlang : 목록 이해, 목록 : 맵 / 2, 목록 : foreach / 2 사용

분류에서Dev

사용자 정의 CType 대 CType 목록 및 list_type

분류에서Dev

Swift에서 "for-In"대신 "forEach"사용

분류에서Dev

목록보기에 대한 사용자 정의보기 내에서 Scrollview가 스크롤되지 않습니다.

분류에서Dev

어떻게되는 setMulti 같은 확인란 목록 사용자 지정 대화 상자를 만드는 방법, 수신 및 송신 데이터?

분류에서Dev

R에서 for 루프 대신 Foreach 및 doparallel

분류에서Dev

목록 및 튜플 목록을 사용한 목록 이해

분류에서Dev

foreach-loop 및 텍스트 기반 인스턴스를 사용한 목록 작성

분류에서Dev

Kotlin의 목록에있는 groupby 및 foreach

분류에서Dev

자바 8 사용하여 목록의 (중첩 된 목록) 목록에서 최대 및 최소 액으로부터 목록을 얻을

분류에서Dev

.clone () 및 forEach () 사용 후 항목이 잘못 작동 함

분류에서Dev

RestTemplate GET 객체 목록 - 왜 사용 ParameterizedTypeReference 대신 개체 배열?

분류에서Dev

var 대신 LINQ 기반 목록에 익명 형식 사용

분류에서Dev

__all__ 모듈에서 튜플 대신 목록 사용

분류에서Dev

C ++에서 배열 대신 목록 사용

분류에서Dev

값 범위 대신 목록을 사용하는 uniform_int_distribution

분류에서Dev

목록에서 글 머리 기호 대신 아이콘 사용

분류에서Dev

cublasSetMatrix 및 cublasSetVector 대신 cudaMemCpy 사용

분류에서Dev

지도 및 대해 forEach를 사용하는 경우

분류에서Dev

for 및 if를 사용하여 세트 대 목록에 걸리는 시간

분류에서Dev

split 및 for 루프를 사용하여 목록에서 문자 대신 단어를 추출하는 방법은 무엇입니까?

분류에서Dev

목록 대신 개체로 하위 목록을 기본 목록에 복사

Related 관련 기사

  1. 1

    foreach 대신 필터 사용

  2. 2

    사용 목록 .C #의 ForEach

  3. 3

    중첩 및 다중 foreach 문 대신 LINQ 또는 Lambda를 사용하는 방법

  4. 4

    대해 forEach, 자바 (8)을 사용하여 목록 목록 (중첩 된 목록)을 생성 스트림

  5. 5

    for-each 루프 대신 Java 스트림을 사용하여 목록 반복 및 필터링

  6. 6

    목록 및 단순화 규칙 : # 대신 @를 사용할 때 더 어렵습니까?

  7. 7

    IntStream 및 대해 forEach를 사용하여 정수의 목록을 작성하는 내 방식의 잘못은 무엇인가?

  8. 8

    Erlang : 목록 이해, 목록 : 맵 / 2, 목록 : foreach / 2 사용

  9. 9

    사용자 정의 CType 대 CType 목록 및 list_type

  10. 10

    Swift에서 "for-In"대신 "forEach"사용

  11. 11

    목록보기에 대한 사용자 정의보기 내에서 Scrollview가 스크롤되지 않습니다.

  12. 12

    어떻게되는 setMulti 같은 확인란 목록 사용자 지정 대화 상자를 만드는 방법, 수신 및 송신 데이터?

  13. 13

    R에서 for 루프 대신 Foreach 및 doparallel

  14. 14

    목록 및 튜플 목록을 사용한 목록 이해

  15. 15

    foreach-loop 및 텍스트 기반 인스턴스를 사용한 목록 작성

  16. 16

    Kotlin의 목록에있는 groupby 및 foreach

  17. 17

    자바 8 사용하여 목록의 (중첩 된 목록) 목록에서 최대 및 최소 액으로부터 목록을 얻을

  18. 18

    .clone () 및 forEach () 사용 후 항목이 잘못 작동 함

  19. 19

    RestTemplate GET 객체 목록 - 왜 사용 ParameterizedTypeReference 대신 개체 배열?

  20. 20

    var 대신 LINQ 기반 목록에 익명 형식 사용

  21. 21

    __all__ 모듈에서 튜플 대신 목록 사용

  22. 22

    C ++에서 배열 대신 목록 사용

  23. 23

    값 범위 대신 목록을 사용하는 uniform_int_distribution

  24. 24

    목록에서 글 머리 기호 대신 아이콘 사용

  25. 25

    cublasSetMatrix 및 cublasSetVector 대신 cudaMemCpy 사용

  26. 26

    지도 및 대해 forEach를 사용하는 경우

  27. 27

    for 및 if를 사용하여 세트 대 목록에 걸리는 시간

  28. 28

    split 및 for 루프를 사용하여 목록에서 문자 대신 단어를 추출하는 방법은 무엇입니까?

  29. 29

    목록 대신 개체로 하위 목록을 기본 목록에 복사

뜨겁다태그

보관