Last active
December 21, 2022 14:14
-
-
Save HironobuIga/1abd6e38fddc5d06f1e7be5a49208704 to your computer and use it in GitHub Desktop.
Chat Screen
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
struct Sample: Identifiable { | |
let id: String | |
var num: Int | |
let date: Date | |
var title: String { "title \(num)" } | |
var content: String { "content \(num)" } | |
init(num: Int) { | |
self.id = UUID().uuidString | |
self.num = num | |
self.date = .now | |
} | |
} | |
struct HeightKey: PreferenceKey { | |
typealias Value = CGFloat | |
static var defaultValue: CGFloat = 0 | |
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { | |
value += nextValue() | |
} | |
} | |
@MainActor | |
final class SampleViewModel: ObservableObject { | |
@Published var list: [Sample] = .init() | |
func didTapAddButton() { | |
list.insert(Sample(num: list.endIndex + 1), at: 0) | |
} | |
func didTapRemoveButton() { | |
guard list.count > 0 else { return } | |
list.remove(at: 0) | |
} | |
} | |
struct SampleScreen: View { | |
@StateObject private var viewModel: SampleViewModel = .init() | |
@State var height: CGFloat = 0 | |
let formatter = ISO8601DateFormatter() | |
var body: some View { | |
VStack(spacing: 0) { | |
list() | |
HStack { | |
Button { | |
viewModel.didTapAddButton() | |
} label: { | |
Text("追加") | |
} | |
Button { | |
viewModel.didTapRemoveButton() | |
} label: { | |
Text("削除") | |
} | |
} | |
} | |
.navigationTitle("List") | |
.navigationBarTitleDisplayMode(.inline) | |
} | |
func list() -> some View { | |
GeometryReader { scrollViewProxy in | |
ScrollView { | |
// 要素数が少ない場合に要素を上詰めするために画面下部にスペースを確保する | |
// 他の要素で画面全体のスペースが埋まる場合は不要なので、高さを0とする | |
Spacer() | |
.frame(height: max(scrollViewProxy.size.height - height, 0)) | |
LazyVStack(alignment: .leading, spacing: 0) { | |
ForEach(viewModel.list) { sample in | |
cell(sample: sample) | |
.rotation3DEffect(.degrees(180), axis: (x: 1, y: 0, z: 0)) | |
} | |
} | |
.overlay { | |
GeometryReader { contentsProxy in | |
// PreferenceKeyを使用しコンテンツの高さを伝える | |
Color.clear.preference( | |
key: HeightKey.self, | |
value: contentsProxy.size.height | |
) | |
} | |
} | |
} | |
.rotation3DEffect(.degrees(180), axis: (x: 1, y: 0, z: 0)) | |
.onPreferenceChange(HeightKey.self) { newHeight in | |
// コンテンツの高さの変化を検知し、\@Stateで保持している | |
// heightに伝えViewに反映させる | |
height = newHeight | |
} | |
} | |
} | |
func cell(sample: Sample) -> some View { | |
VStack(alignment: .leading) { | |
Text(sample.title) | |
Text(sample.content) | |
Text(formatter.string(from: sample.date)) | |
} | |
.padding(.horizontal, 16) | |
.padding(.vertical, 8) | |
} | |
} | |
struct SampleScreen_Previews: PreviewProvider { | |
static var previews: some View { | |
NavigationView { | |
SampleScreen() | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment