Skip to content

Instantly share code, notes, and snippets.

@theoknock
Last active May 25, 2024 05:30
Show Gist options
  • Save theoknock/09a7a9193ceba888d2099bac665bffb7 to your computer and use it in GitHub Desktop.
Save theoknock/09a7a9193ceba888d2099bac665bffb7 to your computer and use it in GitHub Desktop.
A start to a typography style guide layout tool that lets you experiment with various aspects of typography (fonts, sizes, spacing, alignment, and other typographic elements) for branding and product design, ensuring that all textual content remains consistent and aligns with the overall aesthetic and communication goals of your brand or organiz…
import SwiftUI
struct ContentView: View {
@State private var selectedFontName: String = "Times New Roman"
@State private var isPresented: Bool = true
var body: some View {
VStack {
FontSelectorView(selectedFontName: $selectedFontName)
TabView {
P73_DynamicTypeSize()
.tabItem {
Label("One", systemImage: "1.circle")
}
FontWeightsPageView(selectedFontName: $selectedFontName)
.tabItem {
Label("Two", systemImage: "2.circle")
}
FontFamiliesView(selectedFontName: $selectedFontName)
.tabItem {
Label("Three", systemImage: "3.circle")
}
FontCharactersView(selectedFontName: $selectedFontName)
.tabItem {
Label("Four", systemImage: "4.circle")
}
TextStylePairingView(selectedFontName: $selectedFontName)
.tabItem {
Label("Five", systemImage: "5.circle")
}
P180_FoldPaper()
.tabItem {
Label("Six", systemImage: "6.circle")
}
}
}
.blur(radius: (isPresented) ? 0.75 : 0.0)
.sheet(isPresented: $isPresented, content: {
FontFamiliesView(selectedFontName: $selectedFontName)
.foregroundColor(.primary)
.shadow(color: .black, radius: 3)
.background(.ultraThinMaterial)
.clipShape(RoundedRectangle(cornerRadius: 7))
.offset(CGSize(width: 10.0, height: 100.0))
})
}
}
//
// P73_DynamicTypeSize.swift
// Fabula
//
// Created by jasu on 2021/09/06.
// Copyright (c) 2021 jasu All rights reserved.
//
#if os(iOS)
public struct P73_DynamicTypeSize: View {
@Environment(\.dynamicTypeSize) private var dynamicTypeSize: DynamicTypeSize
@State private var currentTypeSize: DynamicTypeSize = .xSmall
let types: [DynamicTypeSize] = [.xSmall, .small, .medium, .large, .xLarge, .xxLarge, .xxxLarge, .accessibility1, .accessibility2, .accessibility3, .accessibility4, .accessibility5]
public init() {}
public var body: some View {
VStack {
HStack(alignment: .top, spacing: 8) {
VStack(alignment: .center) {
DynamicTypeSizeView()
Divider().frame(width: 44)
Text("Settings\n-> Display & Brightness\n-> Text Size")
.font(.caption)
.opacity(0.5)
}
Divider()
ScrollView {
VStack(alignment: .center) {
DynamicTypeSizeView()
.environment(\.dynamicTypeSize, currentTypeSize)
}
}
}
// }
Picker("Dynamic Type Size", selection: $currentTypeSize) {
ForEach(types, id:\.self) { type in
Text(type.typeName())
}
}
.pickerStyle(WheelPickerStyle())
.padding(.bottom)
}
}
}
fileprivate
extension DynamicTypeSize {
func typeName() -> String {
switch self {
case .xSmall: return "xSmall"
case .small: return "small"
case .medium: return "medium"
case .large: return "large"
case .xLarge: return "xLarge"
case .xxLarge: return "xxLarge"
case .xxxLarge: return "xxxLarge"
case .accessibility1: return "accessibility1"
case .accessibility2: return "accessibility2"
case .accessibility3: return "accessibility3"
case .accessibility4: return "accessibility4"
case .accessibility5: return "accessibility5"
default: return ""
}
}
}
fileprivate
struct DynamicTypeSizeView: View {
@Environment(\.dynamicTypeSize) private var dynamicTypeSize: DynamicTypeSize
var body: some View {
VStack(alignment: .center, spacing: 10) {
Text("." + dynamicTypeSize.typeName())
.foregroundColor(Color.primary)
VStack(alignment: .center, spacing: 8.0) {
Group {
Text("largeTitle")
.font(.largeTitle)
Text("Title")
.font(.title)
Text("Title 2")
.font(.title2)
Text("Title 3")
.font(.title3)
Text("Headline")
.font(.headline)
Text("Subheadline")
.font(.subheadline)
Text("Body")
.font(.body)
Text("Callout")
.font(.callout)
Text("Footnote")
.font(.footnote)
Text("Caption")
.font(.caption)
}
Text("Caption 2")
.font(.caption2)
}
}
.padding()
}
}
#else
public struct P73_DynamicTypeSize: View {
public init() {}
public var body: some View {
EmptyView()
}
}
#endif
struct FontSizesView: View {
@Binding var selectedFontName: String
var body: some View {
Group {
List {
#if os(macOS)
VStack(alignment: .leading) {
Text("Extra Large Title 2").font(.custom(selectedFontName, size: UIFont.preferredFont(forTextStyle: .extraLargeTitle2).pointSize))
}
VStack(alignment: .leading) {
Text("Extra Large Title").font(.custom(selectedFontName, size: UIFont.preferredFont(forTextStyle: .extraLargeTitle).pointSize))
}
#endif
VStack(alignment: .leading) {
Text("Large Title").font(.system(Font.TextStyle.largeTitle, design: Font.Design?.some(Font.Design.default), weight: Font.Weight?.some(Font.Weight.regular)))
Text("The size of the large title style.").font(.system(Font.TextStyle.body, design: Font.Design?.some(Font.Design.serif), weight: Font.Weight?.some(Font.Weight.regular)))
// Text("Large Title").font(.custom(selectedFontName, size: UIFont.preferredFont(forTextStyle: .largeTitle).pointSize))/*.dynamicTypeSize(.accessibility1)*/
}
VStack(alignment: .leading) {
Text("Title 1").font(.custom(selectedFontName, size: UIFont.preferredFont(forTextStyle: .title1).pointSize))/*.dynamicTypeSize(.accessibility1)*/
}
VStack(alignment: .leading) {
Text("Title 2").font(.custom(selectedFontName, size: UIFont.preferredFont(forTextStyle: .title2).pointSize))
Text("The size of the Title 2 style").font(.custom(selectedFontName, size: UIFont.preferredFont(forTextStyle: .body).pointSize))
/*.dynamicTypeSize(.accessibility1)*/
}
// .lineSpacing(UIFont.preferredFont(forTextStyle: .footnote).lineHeight)
VStack(alignment: .leading) {
Text("Title 3").font(.custom(selectedFontName, size: UIFont.preferredFont(forTextStyle: .title3).pointSize))/*.dynamicTypeSize(.accessibility1)*/
}
VStack(alignment: .leading) {
Text("Headline").font(.custom(selectedFontName, size: UIFont.preferredFont(forTextStyle: .headline).pointSize))/*.dynamicTypeSize(.accessibility1)*/
}
VStack(alignment: .leading) {
Text("Subheadline").font(.custom(selectedFontName, size: UIFont.preferredFont(forTextStyle: .subheadline).pointSize))/*.dynamicTypeSize(.accessibility1)*/
}
Text("Body").font(.custom(selectedFontName, size: UIFont.preferredFont(forTextStyle: .body).pointSize))/*.dynamicTypeSize(.accessibility1)*/
Text("Callout").font(.custom(selectedFontName, size: UIFont.preferredFont(forTextStyle: .callout).pointSize))/*.dynamicTypeSize(.accessibility1)*/
Text("Caption 1").font(.custom(selectedFontName, size: UIFont.preferredFont(forTextStyle: .caption1).pointSize))/*.dynamicTypeSize(.accessibility1)*/
Text("Caption 2").font(.custom(selectedFontName, size: UIFont.preferredFont(forTextStyle: .caption2).pointSize))/*.dynamicTypeSize(.accessibility1)*/
Text("Footnote").font(.custom(selectedFontName, size: UIFont.preferredFont(forTextStyle: .footnote).pointSize))/*.dynamicTypeSize(.accessibility1)*/
}
}
}
}
struct FontWeightsPageView: View {
@Binding var selectedFontName: String
@State private var currentPage = 0
var body: some View {
TabView(selection: $currentPage) {
ForEach(Array(UIFont.fontNames(forFamilyName: selectedFontName).enumerated()), id: \.element) { index, fontName in
VStack(alignment: .leading) {
Text("\(fontName)")
.font(.custom(fontName, size: UIFont.preferredFont(forTextStyle: .largeTitle).pointSize))
.foregroundStyle(.primary)
Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam.")
.font(.custom(fontName, size: UIFont.preferredFont(forTextStyle: .body).pointSize))
.foregroundStyle(.secondary)
}
// .dynamicTypeSize(DynamicTypeSize.xSm)
}
}
.tabViewStyle(PageTabViewStyle())
}
}
struct FontFamiliesView: View {
@Binding var selectedFontName: String
var body: some View {
NavigationView {
List {
ForEach(UIFont.familyNames.sorted(), id: \.self) { familyName in
Section(header: Text(familyName).font(.headline)) {
ForEach(UIFont.fontNames(forFamilyName: familyName), id: \.self) { fontName in
Text(fontName)
.font(.custom(fontName, size: 16))
.onTapGesture {
selectedFontName = fontName
}
}
}
}
}
.navigationTitle("Font Families")
}
}
}
struct FontCharactersView: View {
@Binding var selectedFontName: String
@State private var selectedCharacter: Character? = nil
var body: some View {
ScrollView {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 40))]) {
ForEach(allCharacters(for: selectedFontName), id: \.self) { character in
Text(String(character))
.font(.custom(selectedFontName, size: selectedCharacter == character ? 48 : 24))
.frame(width: selectedCharacter == character ? 60 : 40, height: selectedCharacter == character ? 60 : 40)
.border(selectedCharacter == character ? Color.primary : Color.secondary)
.background(selectedCharacter == character ? .black : .clear)
.onTapGesture {
if selectedCharacter == character {
selectedCharacter = nil
} else {
selectedCharacter = character
}
}
}
}
.foregroundStyle(.primary)
// .background(characterBackgroundColor)
.padding()
}
.navigationTitle("Characters")
}
func allCharacters(for fontName: String) -> [Character] {
var characters = [Character]()
let font = UIFont(name: fontName, size: 12)!
if let charSet = font.fontDescriptor.object(forKey: .characterSet) as? CharacterSet {
for plane in 0...16 {
let base = plane << 16
for codePoint in base..<(base + 0xFFFF) {
if let scalar = UnicodeScalar(codePoint), charSet.contains(scalar) {
characters.append(Character(scalar))
}
}
}
}
return characters
}
}
struct TextStylePairingView: View {
@Binding var selectedFontName: String
var body: some View {
List {
ForEach(textStylePairs, id: \.0) { headlineStyle, bodyStyle in
Section/*(header: Text(headlineStyleName(for: headlineStyle)).font(.headline)) */{
VStack(alignment: .leading, spacing: 10) {
Text("\(headlineStyleName(for: headlineStyle))")
.font(.custom(selectedFontName, size: UIFont.preferredFont(forTextStyle: headlineStyle).pointSize)).lineSpacing(UIFont.preferredFont(forTextStyle: .body).lineHeight * 0.1618)
Text("\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam.")
.font(.custom(selectedFontName, size: UIFont.preferredFont(forTextStyle: .body).pointSize)).foregroundStyle(.secondary)
Text("\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam.")
.font(.custom(selectedFontName, size: UIFont.preferredFont(forTextStyle: .body).pointSize)).foregroundStyle(.secondary)
}
}
}
.navigationTitle("Text Style Pairings")
}
}
var textStylePairs: [(UIFont.TextStyle, UIFont.TextStyle)] {
return [
// (.headline, .body),
(.largeTitle, .body),
(.title1, .body),
(.title2, .body),
(.title3, .body)
]
}
func preferredFontSize(for textStyle: UIFont.TextStyle) -> CGFloat {
return UIFont.preferredFont(forTextStyle: textStyle).pointSize
}
func headlineStyleName(for textStyle: UIFont.TextStyle) -> String {
switch textStyle {
case .headline: return "Headline"
case .largeTitle: return "Large Title"
case .title1: return "Title 1"
case .title2: return "Title 2"
case .title3: return "Title 3"
default: return "Unknown"
}
}
}
struct FontSelectorView: View {
@Binding var selectedFontName: String
private let familyNames: [String] = {
var allFontNames = [String]()
for family in UIFont.familyNames {
allFontNames.append(family)
}
return allFontNames
}()
var body: some View {
Picker("Select Font", selection: $selectedFontName) {
ForEach(Array(UIFont.familyNames.enumerated()), id: \.element) { index_a, familyName in
Text(familyName).tag(index_a)
.font(.custom(familyName, fixedSize: UIFont.preferredFont(forTextStyle: .headline).pointSize))
// }
}
}
.pickerStyle(MenuPickerStyle())
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import SwiftUI
public struct P180_FoldPaper: View {
@State private var datas = [TreeItem]()
public init() {}
public var body: some View {
ZStack {
FoldPaper(items: $datas)
.padding()
}
.onAppear {
DispatchQueue.main.async {
datas = UIFont.familyNames.sorted().map { familyName in
TreeItem(
title: familyName,
depth: 0,
font: Font.custom(familyName, size: UIFont.preferredFont(forTextStyle: .body).pointSize),
children: UIFont.fontNames(forFamilyName: familyName).map { fontName in
TreeItem(title: fontName, depth: 1, font: Font.custom(fontName, size: UIFont.preferredFont(forTextStyle: .body).pointSize), children: nil)
}
)
}
}
}
}
}
fileprivate
extension Color {
init(hex: UInt) {
self.init(
.sRGB,
red: Double((hex >> 16) & 0xff) / 255,
green: Double((hex >> 08) & 0xff) / 255,
blue: Double((hex >> 00) & 0xff) / 255,
opacity: 1
)
}
}
fileprivate
struct TreeItem: Hashable, Identifiable {
var id: Self { self }
var title: String
var depth: Int
var font: Font
var children: [TreeItem]? = nil
}
fileprivate
struct RowView: View {
let colors = [Color(hex: 0xC4321C), Color(hex: 0xEB6E3B), Color(hex: 0xF4A96B), Color(hex: 0xF3D6A9)]
let item: TreeItem
let index : Int
@Binding var isExpanded: Bool
@Binding var isUnfold: Bool
@State private var isUnfoldParent: Bool = false
let rowHeight: CGFloat = 80
var rowContent: some View {
ZStack(alignment: .leading) {
Rectangle()
.fill(colors[item.depth])
HStack {
VStack {
HStack {
Image(systemName: item.depth == 0 ? "arrow.turn.left.down" : "arrow.turn.down.right")
Text(item.title)
.font(item.font)
Spacer()
}
Spacer()
HStack {
Text("row description")
.font(.caption)
.opacity(0.5)
Spacer()
}
.redacted(reason: .placeholder)
Spacer()
}
.padding(.leading)
.padding(.top)
.padding(.leading, CGFloat(item.depth) * 20)
Spacer()
if item.children != nil {
Image(systemName: "chevron.down")
.rotationEffect(Angle(degrees: isExpanded ? -180 : 0))
.padding(.trailing)
}
}
}
.frame(height: rowHeight)
.clipped()
}
var body: some View {
VStack(spacing: 0) {
rowContent
.overlay(
LinearGradient(colors: [Color.black.opacity(0.8), Color.black.opacity(0.6)], startPoint: .topLeading, endPoint: .bottomTrailing)
.opacity(isUnfold ? 0 : 1)
)
.rotation3DEffect(Angle(degrees: -90 * (isUnfold ? 0 : 1)), axis: (x: 1, y: 0, z: 0), anchor: .top)
.frame(height: isUnfold ? rowHeight * 0.5 : 0, alignment: .top)
.clipShape(Rectangle())
rowContent
.overlay(
LinearGradient(colors: [Color.black.opacity(0.4), Color.black.opacity(0.3)], startPoint: .bottomTrailing, endPoint: .topLeading)
.opacity(isUnfold ? 0 : 1)
)
.rotation3DEffect(Angle(degrees: 90 * (isUnfold ? 0 : 1)), axis: (x: 1, y: 0, z: 0), anchor: .bottom)
.frame(height: isUnfold ? rowHeight * 0.5 : 0, alignment: .bottom)
.clipShape(Rectangle())
}
.onTapGesture {
withAnimation(.easeInOut(duration: 0.5)) {
isExpanded.toggle()
isUnfoldParent = false
}
}
.onAppear {
DispatchQueue.main.async {
withAnimation(.easeInOut(duration: 0.5)) {
isUnfold = true
}
}
}
if item.children != nil {
ZStack {
if isExpanded {
VStack(spacing: 0) {
ForEach(Array(item.children!.enumerated()), id: \.offset) { index, item in
ItemView(item: item, index: index, isUnfold: $isUnfoldParent)
}
}
.onDisappear {
isUnfoldParent = false
}
}
}
}
}
}
fileprivate
struct ItemView: View {
let item: TreeItem
let index: Int
@State private var isExpanded: Bool = false
@Binding var isUnfold: Bool
var body: some View {
VStack(spacing: 0) {
RowView(item: item, index: index, isExpanded: $isExpanded, isUnfold: $isUnfold)
}
}
}
fileprivate
struct FoldPaper: View {
@Binding var items: [TreeItem]
@State private var isUnfold: Bool = true
var body: some View {
ScrollView {
VStack(spacing: 0) {
ForEach(Array(items.enumerated()), id: \.offset) { index, item in
ItemView(item: item, index: index, isUnfold: $isUnfold)
}
}
.clipShape(Rectangle())
}
}
}
struct P180_FoldPaper_Previews: PreviewProvider {
static var previews: some View {
P180_FoldPaper()
}
}
import SwiftUI
public struct P180_FoldPaper: View {
@State private var datas = [TreeItem]()
public init() {}
public var body: some View {
ZStack {
FoldPaper(items: $datas)
.padding()
}
.onAppear {
DispatchQueue.main.async {
datas = UIFont.familyNames.sorted().map { familyName in
TreeItem(
title: familyName,
depth: 0,
font: Font.custom(familyName, size: UIFont.preferredFont(forTextStyle: .body).pointSize),
children: UIFont.fontNames(forFamilyName: familyName).map { fontName in
TreeItem(title: fontName, depth: 1, font: Font.custom(fontName, size: UIFont.preferredFont(forTextStyle: .body).pointSize), children: nil)
}
)
}
}
}
}
}
fileprivate
extension Color {
init(hex: UInt) {
self.init(
.sRGB,
red: Double((hex >> 16) & 0xff) / 255,
green: Double((hex >> 08) & 0xff) / 255,
blue: Double((hex >> 00) & 0xff) / 255,
opacity: 1
)
}
}
fileprivate
struct TreeItem: Hashable, Identifiable {
var id: Self { self }
var title: String
var depth: Int
var font: Font
var children: [TreeItem]? = nil
}
fileprivate
struct RowView: View {
let colors = [Color(hex: 0xC4321C), Color(hex: 0xEB6E3B), Color(hex: 0xF4A96B), Color(hex: 0xF3D6A9)]
let item: TreeItem
let index : Int
@Binding var isExpanded: Bool
@Binding var isUnfold: Bool
@State private var isUnfoldParent: Bool = false
let rowHeight: CGFloat = 80
var rowContent: some View {
ZStack(alignment: .leading) {
Rectangle()
.fill(colors[item.depth])
HStack {
VStack {
HStack {
Image(systemName: item.depth == 0 ? "arrow.turn.left.down" : "arrow.turn.down.right")
Text(item.title)
.font(item.font)
Spacer()
}
Spacer()
HStack {
Text("row description")
.font(.caption)
.opacity(0.5)
Spacer()
}
.redacted(reason: .placeholder)
Spacer()
}
.padding(.leading)
.padding(.top)
.padding(.leading, CGFloat(item.depth) * 20)
Spacer()
if item.children != nil {
Image(systemName: "chevron.down")
.rotationEffect(Angle(degrees: isExpanded ? -180 : 0))
.padding(.trailing)
}
}
}
.frame(height: rowHeight)
.clipped()
}
var body: some View {
VStack(spacing: 0) {
rowContent
.overlay(
LinearGradient(colors: [Color.black.opacity(0.8), Color.black.opacity(0.6)], startPoint: .topLeading, endPoint: .bottomTrailing)
.opacity(isUnfold ? 0 : 1)
)
.rotation3DEffect(Angle(degrees: -90 * (isUnfold ? 0 : 1)), axis: (x: 1, y: 0, z: 0), anchor: .top)
.frame(height: isUnfold ? rowHeight * 0.5 : 0, alignment: .top)
.clipShape(Rectangle())
rowContent
.overlay(
LinearGradient(colors: [Color.black.opacity(0.4), Color.black.opacity(0.3)], startPoint: .bottomTrailing, endPoint: .topLeading)
.opacity(isUnfold ? 0 : 1)
)
.rotation3DEffect(Angle(degrees: 90 * (isUnfold ? 0 : 1)), axis: (x: 1, y: 0, z: 0), anchor: .bottom)
.frame(height: isUnfold ? rowHeight * 0.5 : 0, alignment: .bottom)
.clipShape(Rectangle())
}
.onTapGesture {
withAnimation(.easeInOut(duration: 0.5)) {
isExpanded.toggle()
isUnfoldParent = false
}
}
.onAppear {
DispatchQueue.main.async {
withAnimation(.easeInOut(duration: 0.5)) {
isUnfold = true
}
}
}
if item.children != nil {
ZStack {
if isExpanded {
VStack(spacing: 0) {
ForEach(Array(item.children!.enumerated()), id: \.offset) { index, item in
ItemView(item: item, index: index, isUnfold: $isUnfoldParent)
}
}
.onDisappear {
isUnfoldParent = false
}
}
}
}
}
}
fileprivate
struct ItemView: View {
let item: TreeItem
let index: Int
@State private var isExpanded: Bool = false
@Binding var isUnfold: Bool
var body: some View {
VStack(spacing: 0) {
RowView(item: item, index: index, isExpanded: $isExpanded, isUnfold: $isUnfold)
}
}
}
fileprivate
struct FoldPaper: View {
@Binding var items: [TreeItem]
@State private var isUnfold: Bool = true
var body: some View {
ScrollView {
VStack(spacing: 0) {
ForEach(Array(items.enumerated()), id: \.offset) { index, item in
ItemView(item: item, index: index, isUnfold: $isUnfold)
}
}
.clipShape(Rectangle())
}
}
}
struct P180_FoldPaper_Previews: PreviewProvider {
static var previews: some View {
P180_FoldPaper()
}
}
@theoknock
Copy link
Author

IMG_0804
IMG_0805
IMG_0806
IMG_0807

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment