사용자가 카드 중 하나를 탭하면 전체 화면으로 확장되는 일부 카드를 표시하는 다음 코드가 있습니다.
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] 삭제
몇 마디 만하겠습니다