SwiftUI框架详细解析 (二十五) —— 基于SwiftUI的编辑占位符的使用(二)

版本记录

版本号 时间
V1.0 2021.02.14 星期日

前言

今天翻阅苹果的API文档,发现多了一个框架SwiftUI,这里我们就一起来看一下这个框架。感兴趣的看下面几篇文章。
1. SwiftUI框架详细解析 (一) —— 基本概览(一)
2. SwiftUI框架详细解析 (二) —— 基于SwiftUI的闪屏页的创建(一)
3. SwiftUI框架详细解析 (三) —— 基于SwiftUI的闪屏页的创建(二)
4. SwiftUI框架详细解析 (四) —— 使用SwiftUI进行苹果登录(一)
5. SwiftUI框架详细解析 (五) —— 使用SwiftUI进行苹果登录(二)
6. SwiftUI框架详细解析 (六) —— 基于SwiftUI的导航的实现(一)
7. SwiftUI框架详细解析 (七) —— 基于SwiftUI的导航的实现(二)
8. SwiftUI框架详细解析 (八) —— 基于SwiftUI的动画的实现(一)
9. SwiftUI框架详细解析 (九) —— 基于SwiftUI的动画的实现(二)
10. SwiftUI框架详细解析 (十) —— 基于SwiftUI构建各种自定义图表(一)
11. SwiftUI框架详细解析 (十一) —— 基于SwiftUI构建各种自定义图表(二)
12. SwiftUI框架详细解析 (十二) —— 基于SwiftUI创建Mind-Map UI(一)
13. SwiftUI框架详细解析 (十三) —— 基于SwiftUI创建Mind-Map UI(二)
14. SwiftUI框架详细解析 (十四) —— 基于Firebase Cloud Firestore的SwiftUI iOS程序的持久性添加(一)
15. SwiftUI框架详细解析 (十五) —— 基于Firebase Cloud Firestore的SwiftUI iOS程序的持久性添加(二)
16. SwiftUI框架详细解析 (十六) —— 基于SwiftUI简单App的Dependency Injection应用(一)
17. SwiftUI框架详细解析 (十七) —— 基于SwiftUI简单App的Dependency Injection应用(二)
18. SwiftUI框架详细解析 (十八) —— Firebase Remote Config教程(一)
19. SwiftUI框架详细解析 (十九) —— Firebase Remote Config教程(二)
20. SwiftUI框架详细解析 (二十) —— 基于SwiftUI的Document-Based App的创建(一)
21. SwiftUI框架详细解析 (二十一) —— 基于SwiftUI的Document-Based App的创建(二)
22. SwiftUI框架详细解析 (二十二) —— 基于SwiftUI的AWS AppSync框架的使用(一)
23. SwiftUI框架详细解析 (二十三) —— 基于SwiftUI的AWS AppSync框架的使用(二)
24. SwiftUI框架详细解析 (二十四) —— 基于SwiftUI的编辑占位符的使用(一)

源码

1. Swift

下面就是源码了

1. AppMain.swift
import SwiftUI

@main
struct AppMain: App {
  var body: some Scene {
    WindowGroup {
      QuotesView()
    }
  }
}
2. QuotesView.swift
import SwiftUI

struct QuotesView: View {
  @ObservedObject var viewModel = QuotesViewModel()

  var body: some View {
    ZStack {
      NavigationView {
        List {
          ForEach(viewModel.quotes) { quote in
            row(from: quote)
          }
          .redacted(
            reason: viewModel.shouldHideContent ? .placeholder : []
          )
        }
        .navigationTitle("Quotation")
      }

      if viewModel.quotes.isEmpty {
        ProgressView()
      }
    }
  }

  private func row(from quote: Quote) -> some View {
    HStack(spacing: 12) {
      Image(systemName: quote.iconName)
        .resizable()
        .aspectRatio(nil, contentMode: .fit)
        .frame(width: 20)
        .unredacted()

      VStack(alignment: .leading) {
        Text(quote.content)
          .font(
            .system(
              size: 17,
              weight: .medium,
              design: .rounded
            )
          )

        Text(quote.createdDate, style: .date)
          .font(
            .system(
              size: 15,
              weight: .bold,
              design: .rounded
            )
          )
          .foregroundColor(.secondary)
      }
    }
  }
}
3. QuotesViewModel.swift
import SwiftUI

final class QuotesViewModel: ObservableObject {
  @Published var isLoading = false
  @Published var quotes: [Quote] = []
  @Published var shouldConceal = false

  var shouldHideContent: Bool {
    return shouldConceal || isLoading
  }

  init() {
    beginObserving()

    isLoading = true
    let simulatedRequestDelay = Double.random(in: 1..<3)

    delay(interval: simulatedRequestDelay) {
      withAnimation {
        self.quotes = ModelLoader.bundledQuotes
      }

      let simulatedIngestionDelay = Double.random(in: 1..<3)

      self.delay(interval: simulatedIngestionDelay) {
        self.isLoading = false
      }
    }
  }

  private func delay(interval: TimeInterval, block: @escaping () -> Void) {
    DispatchQueue.main.asyncAfter(deadline: .now() + interval) {
      block()
    }
  }

  private func beginObserving() {
    let center = NotificationCenter.default
    center.addObserver(
      self,
      selector: #selector(appMovedToBackground),
      name: UIApplication.willResignActiveNotification,
      object: nil
    )
    center.addObserver(
      self,
      selector: #selector(appMovedToForeground),
      name: UIApplication.didBecomeActiveNotification,
      object: nil
    )
  }

  @objc private func appMovedToForeground() {
    shouldConceal = false
  }

  @objc private func appMovedToBackground() {
    shouldConceal = true
  }
}
4. ModelLoader.swift
import Foundation

enum ModelLoader {
  static var bundledQuotes: [Quote] {
    guard let url = Bundle.main.url(forResource: "quotes", withExtension: "json") else {
      return []
    }

    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    decoder.dateDecodingStrategy = .secondsSince1970

    do {
      let data = try Data(contentsOf: url)
      return try decoder.decode([Quote].self, from: data)
    } catch {
      print(error)
      return []
    }
  }
}
5. Quote.swift
import Foundation

struct Quote: Decodable, Identifiable, Equatable {
  let id: String
  let content: String
  let createdDate: Date
  let iconName: String

  init() {
    id = "default"
    content = "Focus on your goal. Don't look in any direction but ahead."
    createdDate = Date()
    iconName = "sun.min.fill"
  }
}
6. QuoteOfTheHour.swift
import WidgetKit
import SwiftUI

struct Provider: TimelineProvider {
  func placeholder(in context: Context) -> QuoteEntry {
    QuoteEntry(model: Quote(), date: Date())
  }

  func getSnapshot(in context: Context, completion: @escaping (QuoteEntry) -> Void) {
    let entry = QuoteEntry(model: Quote(), date: Date())
    completion(entry)
  }

  func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) {
    var entries: [QuoteEntry] = []
    var quotes = ModelLoader.bundledQuotes

    let calendar = Calendar.current
    let currentDate = Date()

    for hourOffset in 0..<24 {
      guard let entryDate = calendar.date(byAdding: .hour, value: hourOffset, to: currentDate) else {
        continue
      }

      guard let randomQuote = quotes.randomElement() else {
        continue
      }

      if let index = quotes.firstIndex(of: randomQuote) {
        quotes.remove(at: index)
      }

      entries.append(QuoteEntry(model: randomQuote, date: entryDate))
    }

    let timeline = Timeline(entries: entries, policy: .atEnd)
    completion(timeline)
  }
}

struct QuoteEntry: TimelineEntry {
  let model: Quote
  let date: Date
}

struct QuoteOfTheHourEntryView: View {
  var entry: Provider.Entry

  var body: some View {
    VStack(alignment: .leading) {
      HStack {
        Image(systemName: entry.model.iconName)
          .resizable()
          .aspectRatio(nil, contentMode: .fit)
          .frame(width: 12)
          .unredacted()

        Spacer()

        Text(entry.model.createdDate, style: .date)
          .font(
            .system(
              size: 12,
              weight: .bold,
              design: .rounded
            )
          )
          .foregroundColor(.secondary)
          .multilineTextAlignment(.trailing)
      }

      Text(entry.model.content)
        .font(
          .system(
            size: 16,
            weight: .medium,
            design: .rounded
          )
        )

      Spacer()
    }
    .padding(12)
  }
}

@main
struct QuoteOfTheHour: Widget {
  let kind: String = "QuoteOfTheHour"

  var body: some WidgetConfiguration {
    StaticConfiguration(kind: kind, provider: Provider()) { entry in
      QuoteOfTheHourEntryView(entry: entry)
    }
    .supportedFamilies([.systemSmall, .systemMedium])
    .configurationDisplayName("Quote Of The Hour")
    .description("Your hourly inspiration.")
  }
}

后记

本篇主要讲述了基于SwiftUI的编辑占位符的使用,感兴趣的给个赞或者关注~~~

你可能感兴趣的:(SwiftUI框架详细解析 (二十五) —— 基于SwiftUI的编辑占位符的使用(二))