Add a calendar grid example

This commit is contained in:
Sami Samhuri 2024-02-12 21:34:49 -08:00
parent f74b1c3362
commit fa0554d94f
No known key found for this signature in database
GPG key ID: 4B4195422742FC16
14 changed files with 343 additions and 5 deletions

View file

@ -21,10 +21,15 @@
96088D862B799B1400E062FB /* TextSizingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96088D852B799B1400E062FB /* TextSizingView.swift */; };
96088D882B7AFE7D00E062FB /* BadGoodView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96088D872B7AFE7D00E062FB /* BadGoodView.swift */; };
96088D8A2B7B009400E062FB /* FlexibleSizingUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96088D892B7B009400E062FB /* FlexibleSizingUIView.swift */; };
96088D8E2B7B1BF300E062FB /* BadDayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96088D8D2B7B1BF300E062FB /* BadDayView.swift */; };
96088D902B7B20FE00E062FB /* DayState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96088D8F2B7B20FE00E062FB /* DayState.swift */; };
96088D922B7B220200E062FB /* GoodDayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96088D912B7B220200E062FB /* GoodDayView.swift */; };
96088D942B7B286E00E062FB /* CalendarBadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96088D932B7B286E00E062FB /* CalendarBadView.swift */; };
96088D962B7B300C00E062FB /* CalendarGoodView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96088D952B7B300C00E062FB /* CalendarGoodView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
96088D5E2B797B7700E062FB /* AccessibilityTalk.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AccessibilityTalk.app; sourceTree = BUILT_PRODUCTS_DIR; };
96088D5E2B797B7700E062FB /* Accesibility Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Accesibility Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; };
96088D612B797B7700E062FB /* AccessibilityTalkApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityTalkApp.swift; sourceTree = "<group>"; };
96088D632B797B7700E062FB /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
96088D652B797B7900E062FB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -39,6 +44,11 @@
96088D852B799B1400E062FB /* TextSizingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextSizingView.swift; sourceTree = "<group>"; };
96088D872B7AFE7D00E062FB /* BadGoodView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadGoodView.swift; sourceTree = "<group>"; };
96088D892B7B009400E062FB /* FlexibleSizingUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexibleSizingUIView.swift; sourceTree = "<group>"; };
96088D8D2B7B1BF300E062FB /* BadDayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadDayView.swift; sourceTree = "<group>"; };
96088D8F2B7B20FE00E062FB /* DayState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayState.swift; sourceTree = "<group>"; };
96088D912B7B220200E062FB /* GoodDayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoodDayView.swift; sourceTree = "<group>"; };
96088D932B7B286E00E062FB /* CalendarBadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarBadView.swift; sourceTree = "<group>"; };
96088D952B7B300C00E062FB /* CalendarGoodView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarGoodView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -63,7 +73,7 @@
96088D5F2B797B7700E062FB /* Products */ = {
isa = PBXGroup;
children = (
96088D5E2B797B7700E062FB /* AccessibilityTalk.app */,
96088D5E2B797B7700E062FB /* Accesibility Demo.app */,
);
name = Products;
sourceTree = "<group>";
@ -74,6 +84,11 @@
96088D612B797B7700E062FB /* AccessibilityTalkApp.swift */,
96088D652B797B7900E062FB /* Assets.xcassets */,
96088D872B7AFE7D00E062FB /* BadGoodView.swift */,
96088D8D2B7B1BF300E062FB /* BadDayView.swift */,
96088D912B7B220200E062FB /* GoodDayView.swift */,
96088D932B7B286E00E062FB /* CalendarBadView.swift */,
96088D952B7B300C00E062FB /* CalendarGoodView.swift */,
96088D8F2B7B20FE00E062FB /* DayState.swift */,
96088D632B797B7700E062FB /* ContentView.swift */,
96088D732B7980D700E062FB /* DynamicTypeAdaptiveView.swift */,
96088D792B79827D00E062FB /* ExampleFormBadView.swift */,
@ -114,7 +129,7 @@
);
name = AccessibilityTalk;
productName = AccessibilityTalk;
productReference = 96088D5E2B797B7700E062FB /* AccessibilityTalk.app */;
productReference = 96088D5E2B797B7700E062FB /* Accesibility Demo.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
@ -168,17 +183,22 @@
buildActionMask = 2147483647;
files = (
96088D762B7981DC00E062FB /* FlexibleSizingView.swift in Sources */,
96088D962B7B300C00E062FB /* CalendarGoodView.swift in Sources */,
96088D742B7980D700E062FB /* DynamicTypeAdaptiveView.swift in Sources */,
96088D642B797B7700E062FB /* ContentView.swift in Sources */,
96088D622B797B7700E062FB /* AccessibilityTalkApp.swift in Sources */,
96088D722B797FBA00E062FB /* FlipLayoutAxisView.swift in Sources */,
96088D822B79925200E062FB /* FlexibleSizingGoodUIView.swift in Sources */,
96088D8E2B7B1BF300E062FB /* BadDayView.swift in Sources */,
96088D7A2B79827D00E062FB /* ExampleFormBadView.swift in Sources */,
96088D782B7981EB00E062FB /* ExampleFormGoodView.swift in Sources */,
96088D882B7AFE7D00E062FB /* BadGoodView.swift in Sources */,
96088D8A2B7B009400E062FB /* FlexibleSizingUIView.swift in Sources */,
96088D802B798DBE00E062FB /* FlexibleSizingBadUIView.swift in Sources */,
96088D902B7B20FE00E062FB /* DayState.swift in Sources */,
96088D922B7B220200E062FB /* GoodDayView.swift in Sources */,
96088D862B799B1400E062FB /* TextSizingView.swift in Sources */,
96088D942B7B286E00E062FB /* CalendarBadView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -326,7 +346,7 @@
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = co.1se.AccessibilityTalk;
PRODUCT_NAME = "$(TARGET_NAME)";
PRODUCT_NAME = "Accesibility Demo";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@ -355,7 +375,7 @@
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = co.1se.AccessibilityTalk;
PRODUCT_NAME = "$(TARGET_NAME)";
PRODUCT_NAME = "Accesibility Demo";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";

View file

@ -1,6 +1,10 @@
{
"colors" : [
{
"color" : {
"platform" : "universal",
"reference" : "systemPurpleColor"
},
"idiom" : "universal"
}
],

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 KiB

View file

@ -1,6 +1,7 @@
{
"images" : [
{
"filename" : "AccessibilityTalkAppIcon-1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "geese.jpg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "good-day.jpeg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

View file

@ -0,0 +1,76 @@
//
// DayView.swift
// AccessibilityTalk
//
// Created by Work on 2024-02-12.
//
import SwiftUI
struct BadDayView: View {
@State var state: DayState = DayState()
var body: some View {
ZStack {
if state.snippetCount == 0 {
Color(.systemGray3)
} else {
Image("Geese")
.resizable()
.scaledToFill()
.clipped()
}
date
.font(.headline)
.bold()
.foregroundStyle(.thickMaterial)
.padding(4)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
if state.hasJournal {
Image(systemName: "pencil.circle")
.font(.headline)
.bold()
.foregroundStyle(.thickMaterial)
.padding(4)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
}
snippetDescription
.font(.headline)
.bold()
.foregroundStyle(.thickMaterial)
.padding(4)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
}
}
private var date: some View {
Text("\(state.date.formatted(date: .abbreviated, time: .omitted))")
}
@ViewBuilder
private var snippetDescription: some View {
switch state.snippetCount {
case 0:
EmptyView()
case 1:
Text("1 snippet")
default:
Text("\(state.snippetCount) snippets")
}
}
}
#Preview {
VStack {
BadDayView()
BadDayView(state: DayState(hasJournal: false, snippetCount: 1))
BadDayView(state: DayState(hasJournal: true, snippetCount: 2))
}
}

View file

@ -0,0 +1,35 @@
//
// CalendarBadView.swift
// AccessibilityTalk
//
// Created by Work on 2024-02-12.
//
import SwiftUI
struct CalendarBadView: View {
var body: some View {
LazyVGrid(columns: gridColumns, spacing: 0) {
Group {
BadDayView()
BadDayView(state: DayState(hasJournal: false, snippetCount: 1))
BadDayView(state: DayState(hasJournal: true, snippetCount: 2))
}
}
}
private var gridColumns: [GridItem] {
let item = GridItem(.flexible(), spacing: 0)
return Array(repeating: item, count: 3)
}
}
#Preview {
NavigationStack {
CalendarBadView()
.navigationTitle("❌ Calendar Grid")
.navigationBarTitleDisplayMode(.inline)
}
}

View file

@ -0,0 +1,40 @@
//
// CalendarGoodView.swift
// AccessibilityTalk
//
// Created by Work on 2024-02-12.
//
import SwiftUI
struct CalendarGoodView: View {
@Environment(\.dynamicTypeSize) var typeSize
var body: some View {
ScrollView {
LazyVGrid(columns: gridColumns, spacing: 0) {
Group {
GoodDayView()
GoodDayView(state: DayState(hasJournal: false, snippetCount: 1))
GoodDayView(state: DayState(hasJournal: true, snippetCount: 2))
}
}
}
.navigationTitle("✅ Calendar Grid")
.navigationBarTitleDisplayMode(.inline)
}
private var gridColumns: [GridItem] {
let item = GridItem(.flexible(), spacing: 0)
let count = typeSize.isAccessibilitySize ? 1 : 3
return Array(repeating: item, count: count)
}
}
#Preview {
NavigationStack {
CalendarGoodView()
}
}

View file

@ -28,6 +28,13 @@ struct ContentView: View {
.accessibilityLabel("Good SwiftUI Form")
}
}
Section("5. Calendar Grid") {
NavigationLink("❌ Bad SwiftUI Grid") { CalendarBadView() }
NavigationLink(destination: CalendarGoodView()) {
Text("✅ Good SwiftUI Grid")
.accessibilityLabel("Good SwiftUI Grid")
}
}
}
.navigationTitle("Accessibility Demo")
.navigationBarTitleDisplayMode(.inline)

View file

@ -0,0 +1,16 @@
//
// DayState.swift
// AccessibilityTalk
//
// Created by Work on 2024-02-12.
//
import Foundation
struct DayState: Hashable {
var date: Date = .now
var hasJournal: Bool = false
var snippetCount: Int = 0
var isEmpty: Bool { !hasJournal && snippetCount == 0 }
}

View file

@ -0,0 +1,115 @@
//
// GoodDayView.swift
// AccessibilityTalk
//
// Created by Work on 2024-02-12.
//
import SwiftUI
struct GoodDayView: View {
@State var state: DayState = DayState()
@Environment(\.dynamicTypeSize) var typeSize
var body: some View {
VStack(spacing: 4) {
ZStack {
if state.snippetCount == 0 {
if !typeSize.isAccessibilitySize {
Color(.systemGray3)
}
} else {
Image("IceCube")
.resizable()
.scaledToFit()
.clipped()
}
if !typeSize.isAccessibilitySize {
Group {
date
.font(.headline)
.bold()
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
journalIndicator
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
description
.font(.headline)
.bold()
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
}
.foregroundStyle(.thickMaterial)
.padding(4)
}
}
.accessibilityElement()
if typeSize.isAccessibilitySize {
date
.bold()
.padding(4)
.frame(maxWidth: .infinity, alignment: .leading)
HStack {
description
.frame(maxWidth: .infinity, alignment: .leading)
journalIndicator
}
.padding(4)
}
}
.accessibilityElement()
.accessibilityLabel(accessibilityDescription)
}
private var date: some View {
Text("\(state.date.formatted(date: .abbreviated, time: .omitted))")
}
@ViewBuilder
private var description: some View {
switch state.snippetCount {
case 0:
EmptyView()
case 1:
Text("1 snippet")
default:
Text("\(state.snippetCount) snippets")
}
}
@ViewBuilder
private var journalIndicator: some View {
if state.hasJournal {
Image(systemName: "pencil.circle")
}
}
private var accessibilityDescription: String {
[
state.date.formatted(date: .abbreviated, time: .omitted),
state.isEmpty ? "empty" : nil,
state.snippetCount == 1 ? "1 snippet" : nil,
state.snippetCount > 1 ? "\(state.snippetCount) snippets" : nil,
state.hasJournal ? "journal entry" : nil,
].compactMap { $0 }.joined(separator: ", ")
}
}
#Preview {
ScrollView {
VStack {
GoodDayView()
GoodDayView(state: DayState(hasJournal: false, snippetCount: 1))
GoodDayView(state: DayState(hasJournal: true, snippetCount: 2))
}
}
}