scroll setting works, now need to read scroll offset
commit
9af28ef0df
|
@ -0,0 +1,65 @@
|
|||
import Combine
|
||||
import GRDB
|
||||
import GRDBQuery
|
||||
|
||||
/// A player request can be used with the `@Query` property wrapper in order to
|
||||
/// feed a view with a list of players.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// struct MyView: View {
|
||||
/// @Query(RibbonRequest(ordering: .byName)) private var players: [Ribbon]
|
||||
///
|
||||
/// var body: some View {
|
||||
/// List(players) { player in ... )
|
||||
/// }
|
||||
/// }
|
||||
|
||||
struct RibbonRequest: Queryable {
|
||||
// enum Ordering {
|
||||
// case byScore
|
||||
// case byName
|
||||
// }
|
||||
|
||||
/// The ordering used by the player request.
|
||||
// var ordering: Ordering
|
||||
// var book: String
|
||||
|
||||
|
||||
// MARK: - Queryable Implementation
|
||||
|
||||
static var defaultValue: [Ribbon] { [] }
|
||||
|
||||
func publisher(in appDatabase: AppDatabase) -> AnyPublisher<[Ribbon], Error> {
|
||||
// Build the publisher from the general-purpose read-only access
|
||||
// granted by `appDatabase.reader`.
|
||||
// Some apps will prefer to call a dedicated method of `appDatabase`.
|
||||
ValueObservation
|
||||
.tracking(fetchValue(_:))
|
||||
.publisher(
|
||||
in: appDatabase.reader,
|
||||
// The `.immediate` scheduling feeds the view right on
|
||||
// subscription, and avoids an undesired animation when the
|
||||
// application starts.
|
||||
scheduling: .immediate)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
// This method is not required by Queryable, but it makes it easier
|
||||
func fetchValue(_ db: Database) throws -> [Ribbon] {
|
||||
return try Ribbon.fetchAll(db)
|
||||
|
||||
// if book == "" {
|
||||
// return try Ribbon.filter(bookColumn == Ribbon.randomBook()).fetchAll(db)
|
||||
// } else {
|
||||
// return try Ribbon.filter(bookColumn == book).fetchAll(db)
|
||||
// }
|
||||
// switch ordering {
|
||||
// case .byScore:
|
||||
// return try Ribbon.all().fetchAll(db)
|
||||
// case .byName:
|
||||
// // return try Ribbon.all().orderedByName().fetchAll(db)
|
||||
// return try Ribbon.all().fetchAll(db)
|
||||
// }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
import SwiftUI
|
||||
|
||||
struct ScrollableView<Content: View>: UIViewControllerRepresentable, Equatable {
|
||||
|
||||
// MARK: - Coordinator
|
||||
final class Coordinator: NSObject, UIScrollViewDelegate {
|
||||
|
||||
// MARK: - Properties
|
||||
private let scrollView: UIScrollView
|
||||
var offset: Binding<CGPoint>
|
||||
|
||||
// MARK: - Init
|
||||
init(_ scrollView: UIScrollView, offset: Binding<CGPoint>) {
|
||||
self.scrollView = scrollView
|
||||
self.offset = offset
|
||||
super.init()
|
||||
self.scrollView.delegate = self
|
||||
}
|
||||
|
||||
// MARK: - UIScrollViewDelegate
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
DispatchQueue.main.async {
|
||||
self.offset.wrappedValue = scrollView.contentOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Type
|
||||
typealias UIViewControllerType = UIScrollViewController<Content>
|
||||
|
||||
// MARK: - Properties
|
||||
var offset: Binding<CGPoint>
|
||||
var animationDuration: TimeInterval
|
||||
var showsScrollIndicator: Bool
|
||||
var axis: Axis
|
||||
var content: () -> Content
|
||||
var onScale: ((CGFloat)->Void)?
|
||||
var disableScroll: Bool
|
||||
var forceRefresh: Bool
|
||||
var stopScrolling: Binding<Bool>
|
||||
private let scrollViewController: UIViewControllerType
|
||||
|
||||
// MARK: - Init
|
||||
init(_ offset: Binding<CGPoint>, animationDuration: TimeInterval, showsScrollIndicator: Bool = true, axis: Axis = .vertical, onScale: ((CGFloat)->Void)? = nil, disableScroll: Bool = false, forceRefresh: Bool = false, stopScrolling: Binding<Bool> = .constant(false), @ViewBuilder content: @escaping () -> Content) {
|
||||
self.offset = offset
|
||||
self.onScale = onScale
|
||||
self.animationDuration = animationDuration
|
||||
self.content = content
|
||||
self.showsScrollIndicator = showsScrollIndicator
|
||||
self.axis = axis
|
||||
self.disableScroll = disableScroll
|
||||
self.forceRefresh = forceRefresh
|
||||
self.stopScrolling = stopScrolling
|
||||
self.scrollViewController = UIScrollViewController(rootView: self.content(), offset: self.offset, axis: self.axis, onScale: self.onScale)
|
||||
}
|
||||
|
||||
// MARK: - Updates
|
||||
func makeUIViewController(context: UIViewControllerRepresentableContext<Self>) -> UIViewControllerType {
|
||||
self.scrollViewController
|
||||
}
|
||||
|
||||
func updateUIViewController(_ viewController: UIViewControllerType, context: UIViewControllerRepresentableContext<Self>) {
|
||||
|
||||
viewController.scrollView.showsVerticalScrollIndicator = self.showsScrollIndicator
|
||||
viewController.scrollView.showsHorizontalScrollIndicator = self.showsScrollIndicator
|
||||
viewController.updateContent(self.content)
|
||||
|
||||
let duration: TimeInterval = self.duration(viewController)
|
||||
let newValue: CGPoint = self.offset.wrappedValue
|
||||
viewController.scrollView.isScrollEnabled = !self.disableScroll
|
||||
|
||||
if self.stopScrolling.wrappedValue {
|
||||
viewController.scrollView.setContentOffset(viewController.scrollView.contentOffset, animated:false)
|
||||
return
|
||||
}
|
||||
|
||||
guard duration != .zero else {
|
||||
viewController.scrollView.contentOffset = newValue
|
||||
return
|
||||
}
|
||||
|
||||
UIView.animate(withDuration: duration, delay: 0, options: [.allowUserInteraction, .curveEaseInOut, .beginFromCurrentState], animations: {
|
||||
viewController.scrollView.contentOffset = newValue
|
||||
}, completion: nil)
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(self.scrollViewController.scrollView, offset: self.offset)
|
||||
}
|
||||
|
||||
//Calcaulte max offset
|
||||
private func newContentOffset(_ viewController: UIViewControllerType, newValue: CGPoint) -> CGPoint {
|
||||
|
||||
let maxOffsetViewFrame: CGRect = viewController.view.frame
|
||||
let maxOffsetFrame: CGRect = viewController.hostingController.view.frame
|
||||
let maxOffsetX: CGFloat = maxOffsetFrame.maxX - maxOffsetViewFrame.maxX
|
||||
let maxOffsetY: CGFloat = maxOffsetFrame.maxY - maxOffsetViewFrame.maxY
|
||||
|
||||
return CGPoint(x: min(newValue.x, maxOffsetX), y: min(newValue.y, maxOffsetY))
|
||||
}
|
||||
|
||||
//Calculate animation speed
|
||||
private func duration(_ viewController: UIViewControllerType) -> TimeInterval {
|
||||
|
||||
var diff: CGFloat = 0
|
||||
|
||||
switch axis {
|
||||
case .horizontal:
|
||||
diff = abs(viewController.scrollView.contentOffset.x - self.offset.wrappedValue.x)
|
||||
default:
|
||||
diff = abs(viewController.scrollView.contentOffset.y - self.offset.wrappedValue.y)
|
||||
}
|
||||
|
||||
if diff == 0 {
|
||||
return .zero
|
||||
}
|
||||
|
||||
let percentageMoved = diff / UIScreen.main.bounds.height
|
||||
|
||||
return self.animationDuration * min(max(TimeInterval(percentageMoved), 0.25), 1)
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
static func == (lhs: ScrollableView, rhs: ScrollableView) -> Bool {
|
||||
return !lhs.forceRefresh && lhs.forceRefresh == rhs.forceRefresh
|
||||
}
|
||||
}
|
||||
|
||||
final class UIScrollViewController<Content: View> : UIViewController, ObservableObject {
|
||||
|
||||
// MARK: - Properties
|
||||
var offset: Binding<CGPoint>
|
||||
var onScale: ((CGFloat)->Void)?
|
||||
let hostingController: UIHostingController<Content>
|
||||
private let axis: Axis
|
||||
lazy var scrollView: UIScrollView = {
|
||||
|
||||
let scrollView = UIScrollView()
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.canCancelContentTouches = true
|
||||
scrollView.delaysContentTouches = true
|
||||
scrollView.scrollsToTop = false
|
||||
scrollView.backgroundColor = .clear
|
||||
|
||||
if self.onScale != nil {
|
||||
scrollView.addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: #selector(self.onGesture)))
|
||||
}
|
||||
|
||||
return scrollView
|
||||
}()
|
||||
|
||||
@objc func onGesture(gesture: UIPinchGestureRecognizer) {
|
||||
self.onScale?(gesture.scale)
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
init(rootView: Content, offset: Binding<CGPoint>, axis: Axis, onScale: ((CGFloat)->Void)?) {
|
||||
self.offset = offset
|
||||
self.hostingController = UIHostingController<Content>(rootView: rootView)
|
||||
self.hostingController.view.backgroundColor = .clear
|
||||
self.axis = axis
|
||||
self.onScale = onScale
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
// MARK: - Update
|
||||
func updateContent(_ content: () -> Content) {
|
||||
|
||||
self.hostingController.rootView = content()
|
||||
self.scrollView.addSubview(self.hostingController.view)
|
||||
|
||||
var contentSize: CGSize = self.hostingController.view.intrinsicContentSize
|
||||
|
||||
switch axis {
|
||||
case .vertical:
|
||||
contentSize.width = self.scrollView.frame.width
|
||||
case .horizontal:
|
||||
contentSize.height = self.scrollView.frame.height
|
||||
}
|
||||
|
||||
self.hostingController.view.frame.size = contentSize
|
||||
self.scrollView.contentSize = contentSize
|
||||
self.view.updateConstraintsIfNeeded()
|
||||
self.view.layoutIfNeeded()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
self.view.addSubview(self.scrollView)
|
||||
self.createConstraints()
|
||||
self.view.setNeedsUpdateConstraints()
|
||||
self.view.updateConstraintsIfNeeded()
|
||||
self.view.layoutIfNeeded()
|
||||
}
|
||||
|
||||
// MARK: - Constraints
|
||||
fileprivate func createConstraints() {
|
||||
NSLayoutConstraint.activate([
|
||||
self.scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
|
||||
self.scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
|
||||
self.scrollView.topAnchor.constraint(equalTo: self.view.topAnchor),
|
||||
self.scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
|
||||
])
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
import Foundation
|
||||
import GRDB
|
||||
|
||||
/// AppDatabase lets the application access the database.
|
||||
///
|
||||
/// It applies the pratices recommended at
|
||||
/// <https://github.com/groue/GRDB.swift/blob/master/Documentation/GoodPracticesForDesigningRecordTypes.md>
|
||||
struct AppDatabase {
|
||||
/// Creates an `AppDatabase`, and make sure the database schema is ready.
|
||||
init(_ dbWriter: any DatabaseWriter) throws {
|
||||
self.dbWriter = dbWriter
|
||||
try migrator.migrate(dbWriter)
|
||||
}
|
||||
|
||||
/// Provides access to the database.
|
||||
///
|
||||
/// Application can use a `DatabasePool`, while SwiftUI previews and tests
|
||||
/// can use a fast in-memory `DatabaseQueue`.
|
||||
///
|
||||
/// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections>
|
||||
private let dbWriter: any DatabaseWriter
|
||||
|
||||
/// The DatabaseMigrator that defines the database schema.
|
||||
///
|
||||
/// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/migrations>
|
||||
private var migrator: DatabaseMigrator {
|
||||
var migrator = DatabaseMigrator()
|
||||
|
||||
#if DEBUG
|
||||
// Speed up development by nuking the database when migrations change
|
||||
// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/migrations>
|
||||
migrator.eraseDatabaseOnSchemaChange = true
|
||||
#endif
|
||||
|
||||
migrator.registerMigration("createLine") { db in
|
||||
// Create a table
|
||||
// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseschema>
|
||||
try db.create(table: "Line") { t in
|
||||
t.autoIncrementedPrimaryKey("id")
|
||||
t.column("body", .text).notNull()
|
||||
t.column("chap", .integer).notNull()
|
||||
t.column("book", .text).notNull()
|
||||
t.column("verse", .integer)
|
||||
}
|
||||
|
||||
try db.create(table: "Ribbon") { t in
|
||||
t.autoIncrementedPrimaryKey("id")
|
||||
t.column("book", .text).notNull()
|
||||
t.column("scrollOffset", .integer).notNull()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Migrations for future application versions will be inserted here:
|
||||
// migrator.registerMigration(...) { db in
|
||||
// ...
|
||||
// }
|
||||
|
||||
return migrator
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Database Access: Writes
|
||||
|
||||
|
||||
func load<T: Decodable>(_ filename: String) -> T {
|
||||
let data: Data
|
||||
|
||||
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
|
||||
else {
|
||||
fatalError("Couldn't find \(filename) in main bundle.")
|
||||
}
|
||||
|
||||
do {
|
||||
data = try Data(contentsOf: file)
|
||||
} catch {
|
||||
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
|
||||
}
|
||||
|
||||
do {
|
||||
let decoder = JSONDecoder()
|
||||
return try decoder.decode(T.self, from: data)
|
||||
} catch {
|
||||
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
|
||||
}
|
||||
}
|
||||
|
||||
extension AppDatabase {
|
||||
|
||||
/// Create random Lines if the database is empty.
|
||||
func createRandomLinesIfEmpty() throws {
|
||||
|
||||
let imports : JsonImport = load("john_export.json")
|
||||
|
||||
try dbWriter.write { db in
|
||||
if try Line.all().isEmpty(db) {
|
||||
for l in imports.lines {
|
||||
print("here")
|
||||
print(l.body)
|
||||
_ = try l.inserted(db)
|
||||
}
|
||||
// print("cat")
|
||||
// try createRandomLines(db)
|
||||
_ = try Ribbon(id: nil, book: "bible.john", scrollOffset: 0).inserted(db)
|
||||
_ = try Ribbon(id: nil, book: "bible.john", scrollOffset: 2000).inserted(db)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static let uiTestLines = [
|
||||
// Line(id: nil, name: "Arthur", score: 5),
|
||||
// Line(id: nil, name: "Barbara", score: 6),
|
||||
// Line(id: nil, name: "Craig", score: 8),
|
||||
// Line(id: nil, name: "David", score: 4),
|
||||
// Line(id: nil, name: "Elena", score: 1),
|
||||
// Line(id: nil, name: "Frederik", score: 2),
|
||||
// Line(id: nil, name: "Gilbert", score: 7),
|
||||
// Line(id: nil, name: "Henriette", score: 3)]
|
||||
|
||||
// func createLinesForUITests() throws {
|
||||
// try dbWriter.write { db in
|
||||
// try AppDatabase.uiTestLines.forEach { Line in
|
||||
// _ = try Line.inserted(db) // insert but ignore inserted id
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// Support for `createRandomLinesIfEmpty()` and `refreshLines()`.
|
||||
private func createRandomLines(_ db: Database) throws {
|
||||
for _ in 0..<500 {
|
||||
_ = try Line.makeRandom().inserted(db) // insert but ignore inserted id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Database Access: Reads
|
||||
|
||||
// This demo app does not provide any specific reading method, and instead
|
||||
// gives an unrestricted read-only access to the rest of the application.
|
||||
// In your app, you are free to choose another path, and define focused
|
||||
// reading methods.
|
||||
extension AppDatabase {
|
||||
/// Provides a read-only access to the database
|
||||
var reader: DatabaseReader {
|
||||
dbWriter
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
//
|
||||
// ContentView.swift
|
||||
// gloss
|
||||
//
|
||||
// Created by Saint on 10/23/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import GRDB
|
||||
import GRDBQuery
|
||||
import Introspect
|
||||
import os
|
||||
let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "network")
|
||||
|
||||
// var curBook = "John"
|
||||
|
||||
extension UINavigationController {
|
||||
override open func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let standard = UINavigationBarAppearance()
|
||||
standard.backgroundColor = .red //When you scroll or you have title (small one)
|
||||
|
||||
let compact = UINavigationBarAppearance()
|
||||
compact.backgroundColor = .green //compact-height
|
||||
|
||||
let scrollEdge = UINavigationBarAppearance()
|
||||
scrollEdge.backgroundColor = .blue //When you have large title
|
||||
|
||||
navigationBar.standardAppearance = standard
|
||||
navigationBar.compactAppearance = compact
|
||||
navigationBar.scrollEdgeAppearance = scrollEdge
|
||||
}
|
||||
}
|
||||
|
||||
struct BlueButtonStyle: ButtonStyle {
|
||||
|
||||
func makeBody(configuration: Self.Configuration) -> some View {
|
||||
configuration.label
|
||||
.font(.headline)
|
||||
.frame(width: 160)
|
||||
.contentShape(Rectangle())
|
||||
.foregroundColor(configuration.isPressed ? Color.white.opacity(0.5) : Color.black)
|
||||
.background(configuration.isPressed ? Color.purple.opacity(0.5) : Color.purple)
|
||||
.listRowBackground(configuration.isPressed ? Color.blue.opacity(0.5) : Color.black)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct SwitchButton : View {
|
||||
var ribbon: Ribbon
|
||||
@Binding var selectedRibbon : Ribbon?
|
||||
@Binding var book : String
|
||||
@Binding var scrollOffset : CGFloat
|
||||
|
||||
var body: some View {
|
||||
Button("meow") {
|
||||
book = ribbon.book
|
||||
selectedRibbon = ribbon
|
||||
Print(ribbon.scrollOffset)
|
||||
Print(scrollOffset)
|
||||
scrollOffset = CGFloat(ribbon.scrollOffset)
|
||||
//Print("hellosdf dddd")
|
||||
}
|
||||
.buttonStyle(BlueButtonStyle())
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView: View {
|
||||
|
||||
@State var viewState = CGSize.zero
|
||||
@State var pulledOut = CGSize.zero
|
||||
@State var taskTitle : String = "FIRST DOGGG"
|
||||
@State var curBook : String = "Matthew"
|
||||
// @State var scrollTo1 : Int64?
|
||||
@State var selectedLine : Int64?
|
||||
@State var scrollOffset = CGFloat()
|
||||
@State var selectedRibbon: Ribbon!
|
||||
|
||||
@Query(LineRequest(ordering: .byScore, book: "bible.john")) private var lines: [Line]
|
||||
|
||||
@Query(RibbonRequest()) private var ribbons: [Ribbon]
|
||||
|
||||
// @Query<LineRequest> private var lines: [Line]
|
||||
|
||||
init() {
|
||||
UITableView.appearance().backgroundColor = UIColor(Color(red: 0.2, green: 0.2, blue: 0.2))
|
||||
// _lines = Query(LineRequest(ordering: .byScore, book: curBook))
|
||||
}
|
||||
|
||||
// @Environment(\.managedObjectContext) private var viewContext
|
||||
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ZStack{
|
||||
VStack{
|
||||
ForEach(ribbons) { ribbon in
|
||||
SwitchButton(ribbon: ribbon,
|
||||
selectedRibbon: $selectedRibbon,
|
||||
book: $lines.book,
|
||||
scrollOffset: $scrollOffset)
|
||||
.buttonStyle(BlueButtonStyle())
|
||||
}
|
||||
}
|
||||
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .topLeading)
|
||||
.background(Color.red)
|
||||
|
||||
// VStack (spacing: 0){
|
||||
ScrollViewReader { proxy in
|
||||
//ScrollableView($offset, animationDuration: 0) {
|
||||
ScrollView {
|
||||
// List {
|
||||
LazyVStack {
|
||||
ForEach(lines) { line in
|
||||
Text(String(line.body))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.contentShape(Rectangle())
|
||||
.font(Font.custom("AveriaSerifLibre-Regular", size: 30))
|
||||
.listRowBackground(Color(red: 0.2, green: 0.8, blue: 0.2))
|
||||
.foregroundColor(Color.white)
|
||||
.listRowInsets(EdgeInsets())
|
||||
.padding(EdgeInsets(top: 10, leading: 20, bottom: 40, trailing: 20))
|
||||
.listRowSeparator(.hidden)
|
||||
.id(line.id!)
|
||||
.onTapGesture {
|
||||
selectedLine = line.id!
|
||||
//Print(selectedLine)
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
.background(Color(red: 0.2, green: 0.2, blue: 0.2))
|
||||
//.onChange(of: scrollTo1) { scrollTo in
|
||||
// // scrollTo1 = 0
|
||||
// let target = scrollTo
|
||||
// //Print("hellosdf")
|
||||
// //Print("value:", target)
|
||||
// //Print("value2:", scrollTo)
|
||||
// proxy.scrollTo(target, anchor: .top)
|
||||
//}
|
||||
}
|
||||
.introspectScrollView { scrollView in
|
||||
//let width = scrollView.contentSize.width - scrollView.frame.width
|
||||
//Print(scrollView.contentOffset.x)
|
||||
scrollView.contentOffset.y = scrollOffset
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
|
||||
}
|
||||
// }
|
||||
.background(Color(red: 0.2, green: 0.2, blue: 0.2))
|
||||
.frame(width: geometry.size.width - 50)
|
||||
.offset(x:30 , y:0)
|
||||
.offset(x: pulledOut.width)
|
||||
.offset(x: viewState.width, y: viewState.height)
|
||||
.onAppear {
|
||||
if (selectedRibbon != nil) {
|
||||
Print(selectedRibbon)
|
||||
//scrollOffset.y = scrollOffset
|
||||
}
|
||||
}
|
||||
.gesture(
|
||||
DragGesture()
|
||||
.onChanged { gesture in
|
||||
// logger.error("hello222")
|
||||
// NSLog("hellooo")
|
||||
//Print(viewState.width)
|
||||
viewState.width = gesture.translation.width
|
||||
//offset.y = gesture.translation.width
|
||||
// logger.log("hello")
|
||||
}
|
||||
.onEnded { _ in
|
||||
var pulledOutWidth = CGFloat(0)
|
||||
if (viewState.width < 0) {
|
||||
pulledOutWidth = CGFloat(0)
|
||||
}
|
||||
else if abs(viewState.width + pulledOut.width ) > 30 {
|
||||
pulledOutWidth = CGFloat(200)
|
||||
}
|
||||
|
||||
withAnimation(.spring(response: 0.2)) {
|
||||
pulledOut.width = pulledOutWidth
|
||||
viewState = .zero
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private struct LineRow: View {
|
||||
var line: Line
|
||||
|
||||
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
|
||||
//Print("Here I am", line.body)
|
||||
Text(line.body)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let itemFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .short
|
||||
formatter.timeStyle = .medium
|
||||
return formatter
|
||||
}()
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
|
||||
ContentView().environment(\.appDatabase, .random())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
extension View {
|
||||
func Print(_ vars: Any...) -> some View {
|
||||
for v in vars { print(v) }
|
||||
return EmptyView()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>AveriaSerifLibre-Regular.ttf</string>
|
||||
<string>xe-Dogma-Bold.ttf</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,11 @@
|
|||
//
|
||||
// JsonImport.swift
|
||||
// gloss
|
||||
//
|
||||
// Created by Saint on 2/24/23.
|
||||
//
|
||||
|
||||
struct JsonImport: Decodable {
|
||||
var lines : [Line]
|
||||
// var segs: [Seg]
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
import GRDB
|
||||
/// The Line struct.
|
||||
///
|
||||
/// Identifiable conformance supports SwiftUI list animations, and type-safe
|
||||
/// GRDB primary key methods.
|
||||
/// Equatable conformance supports tests.
|
||||
struct Line: Identifiable, Equatable {
|
||||
/// The player id.
|
||||
///
|
||||
/// Int64 is the recommended type for auto-incremented database ids.
|
||||
/// Use nil for players that are not inserted yet in the database.
|
||||
var id: Int64?
|
||||
var chap: Int
|
||||
var verse: Int
|
||||
var body: String
|
||||
var book: String
|
||||
}
|
||||
|
||||
extension Line {
|
||||
private static let names = [
|
||||
"Arthur", "Anita", "Barbara", "Bernard", "Craig", "Chiara", "David",
|
||||
"Dean", "Éric", "Elena", "Fatima", "Frederik", "Gilbert", "Georgette",
|
||||
"Henriette", "Hassan", "Ignacio", "Irene", "Julie", "Jack", "Karl",
|
||||
"Kristel", "Louis", "Liz", "Masashi", "Mary", "Noam", "Nicole",
|
||||
"Ophelie", "Oleg", "Pascal", "Patricia", "Quentin", "Quinn", "Raoul",
|
||||
"Rachel", "Stephan", "Susie", "Tristan", "Tatiana", "Ursule", "Urbain",
|
||||
"Victor", "Violette", "Wilfried", "Wilhelmina", "Yvon", "Yann",
|
||||
"Zazie", "Zoé"]
|
||||
|
||||
|
||||
private static let books = [
|
||||
"John", "Matthew", "Imitation of Christ"]
|
||||
|
||||
/// Creates a new player with empty name and zero score
|
||||
// static func new() -> Line {
|
||||
// Line(id: nil, chap: 1, body: "")
|
||||
// }
|
||||
|
||||
// Creates a new player with random name and random score
|
||||
static func makeRandom() -> Line {
|
||||
Line(id: nil, chap: randomScore(), verse: 1, body: randomName(), book: randomBook())
|
||||
}
|
||||
|
||||
/// Returns a random name
|
||||
static func randomName() -> String {
|
||||
names.randomElement()!
|
||||
}
|
||||
|
||||
/// Returns a random score
|
||||
static func randomScore() -> Int {
|
||||
10 * Int.random(in: 0...100)
|
||||
}
|
||||
|
||||
static func randomBook() -> String {
|
||||
books.randomElement()!
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Persistence
|
||||
|
||||
/// Make Line a Codable Record.
|
||||
///
|
||||
/// See <https://github.com/groue/GRDB.swift/blob/master/README.md#records>
|
||||
extension Line: Codable, FetchableRecord, MutablePersistableRecord {
|
||||
// Define database columns from CodingKeys
|
||||
fileprivate enum Columns {
|
||||
static let id = Column(CodingKeys.id)
|
||||
static let chap = Column(CodingKeys.chap)
|
||||
}
|
||||
|
||||
/// Updates a player id after it has been inserted in the database.
|
||||
mutating func didInsert(_ inserted: InsertionSuccess) {
|
||||
id = inserted.rowID
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Line Database Requests
|
||||
|
||||
/// Define some player requests used by the application.
|
||||
///
|
||||
/// See <https://github.com/groue/GRDB.swift/blob/master/README.md#requests>
|
||||
/// See <https://github.com/groue/GRDB.swift/blob/master/Documentation/GoodPracticesForDesigningRecordTypes.md>
|
||||
extension DerivableRequest<Line> {
|
||||
/// A request of players ordered by name.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// let players: [Line] = try dbWriter.read { db in
|
||||
/// try Line.all().orderedByName().fetchAll(db)
|
||||
/// }
|
||||
//func orderedByName() -> Self {
|
||||
// // Sort by name in a localized case insensitive fashion
|
||||
// // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
|
||||
// order(Line.Columns.name.collating(.localizedCaseInsensitiveCompare))
|
||||
//}
|
||||
|
||||
///// A request of players ordered by score.
|
||||
/////
|
||||
///// For example:
|
||||
/////
|
||||
///// let players: [Line] = try dbWriter.read { db in
|
||||
///// try Line.all().orderedByScore().fetchAll(db)
|
||||
///// }
|
||||
///// let bestLine: Line? = try dbWriter.read { db in
|
||||
///// try Line.all().orderedByScore().fetchOne(db)
|
||||
///// }
|
||||
//func orderedByScore() -> Self {
|
||||
// // Sort by descending score, and then by name, in a
|
||||
// // localized case insensitive fashion
|
||||
// // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
|
||||
// order(
|
||||
// Line.Columns.score.desc,
|
||||
// Line.Columns.name.collating(.localizedCaseInsensitiveCompare))
|
||||
//}
|
||||
}
|
||||
//
|
||||
// Line.swift
|
||||
// gloss
|
||||
//
|
||||
// Created by Saint on 2/18/23.
|
||||
//
|
||||
|
||||
import Foundation
|
|
@ -0,0 +1,65 @@
|
|||
import Combine
|
||||
import GRDB
|
||||
import GRDBQuery
|
||||
|
||||
/// A player request can be used with the `@Query` property wrapper in order to
|
||||
/// feed a view with a list of players.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// struct MyView: View {
|
||||
/// @Query(LineRequest(ordering: .byName)) private var players: [Line]
|
||||
///
|
||||
/// var body: some View {
|
||||
/// List(players) { player in ... )
|
||||
/// }
|
||||
/// }
|
||||
|
||||
var bookColumn = Column("book")
|
||||
struct LineRequest: Queryable {
|
||||
enum Ordering {
|
||||
case byScore
|
||||
case byName
|
||||
}
|
||||
|
||||
/// The ordering used by the player request.
|
||||
var ordering: Ordering
|
||||
var book: String
|
||||
|
||||
|
||||
// MARK: - Queryable Implementation
|
||||
|
||||
static var defaultValue: [Line] { [] }
|
||||
|
||||
func publisher(in appDatabase: AppDatabase) -> AnyPublisher<[Line], Error> {
|
||||
// Build the publisher from the general-purpose read-only access
|
||||
// granted by `appDatabase.reader`.
|
||||
// Some apps will prefer to call a dedicated method of `appDatabase`.
|
||||
ValueObservation
|
||||
.tracking(fetchValue(_:))
|
||||
.publisher(
|
||||
in: appDatabase.reader,
|
||||
// The `.immediate` scheduling feeds the view right on
|
||||
// subscription, and avoids an undesired animation when the
|
||||
// application starts.
|
||||
scheduling: .immediate)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
// This method is not required by Queryable, but it makes it easier
|
||||
// to test LineRequest.
|
||||
func fetchValue(_ db: Database) throws -> [Line] {
|
||||
if book == "" {
|
||||
return try Line.filter(bookColumn == Line.randomBook()).fetchAll(db)
|
||||
} else {
|
||||
return try Line.filter(bookColumn == book).fetchAll(db)
|
||||
}
|
||||
// switch ordering {
|
||||
// case .byScore:
|
||||
// return try Line.all().fetchAll(db)
|
||||
// case .byName:
|
||||
// // return try Line.all().orderedByName().fetchAll(db)
|
||||
// return try Line.all().fetchAll(db)
|
||||
// }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
import GRDB
|
||||
import Foundation
|
||||
|
||||
|
||||
extension AppDatabase {
|
||||
/// The database for the application
|
||||
static let shared = makeShared()
|
||||
|
||||
private static func makeShared() -> AppDatabase {
|
||||
do {
|
||||
// Pick a folder for storing the SQLite database, as well as
|
||||
// the various temporary files created during normal database
|
||||
// operations (https://sqlite.org/tempfiles.html).
|
||||
let fileManager = FileManager()
|
||||
let folderURL = try fileManager
|
||||
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
|
||||
.appendingPathComponent("database", isDirectory: true)
|
||||
|
||||
// Support for tests: delete the database if requested
|
||||
if CommandLine.arguments.contains("-reset") {
|
||||
try? fileManager.removeItem(at: folderURL)
|
||||
}
|
||||
|
||||
// Create the database folder if needed
|
||||
try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true)
|
||||
|
||||
// Connect to a database on disk
|
||||
// See https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections
|
||||
let dbURL = folderURL.appendingPathComponent("db.sqlite")
|
||||
let dbPool = try DatabasePool(path: dbURL.path)
|
||||
|
||||
// Create the AppDatabase
|
||||
let appDatabase = try AppDatabase(dbPool)
|
||||
|
||||
// // Prepare the database with test fixtures if requested
|
||||
// if CommandLine.arguments.contains("-fixedTestData") {
|
||||
// try appDatabase.createPlayersForUITests()
|
||||
// } else {
|
||||
// // Otherwise, populate the database if it is empty, for better
|
||||
// // demo purpose.
|
||||
// try appDatabase.createRandomPlayersIfEmpty()
|
||||
// }
|
||||
try appDatabase.createRandomLinesIfEmpty()
|
||||
|
||||
|
||||
return appDatabase
|
||||
} catch {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// fatalError() causes the application to generate a crash log and terminate.
|
||||
//
|
||||
// Typical reasons for an error here include:
|
||||
// * The parent directory cannot be created, or disallows writing.
|
||||
// * The database is not accessible, due to permissions or data protection when the device is locked.
|
||||
// * The device is out of space.
|
||||
// * The database could not be migrated to its latest schema version.
|
||||
// Check the error message to determine what the actual problem was.
|
||||
fatalError("Unresolved error \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an empty database for SwiftUI previews
|
||||
static func empty() -> AppDatabase {
|
||||
// Connect to an in-memory database
|
||||
// See https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections
|
||||
let dbQueue = try! DatabaseQueue()
|
||||
return try! AppDatabase(dbQueue)
|
||||
}
|
||||
|
||||
/// Creates a database full of random players for SwiftUI previews
|
||||
static func random() -> AppDatabase {
|
||||
let appDatabase = empty()
|
||||
try! appDatabase.createRandomLinesIfEmpty()
|
||||
return appDatabase
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// Ribbon.swift
|
||||
// gloss
|
||||
//
|
||||
// Created by Saint on 2/24/23.
|
||||
//
|
||||
|
||||
import GRDB
|
||||
/// The Line struct.
|
||||
///
|
||||
/// Identifiable conformance supports SwiftUI list animations, and type-safe
|
||||
/// GRDB primary key methods.
|
||||
/// Equatable conformance supports tests.
|
||||
struct Ribbon: Identifiable, Equatable {
|
||||
/// The player id.
|
||||
///
|
||||
/// Int64 is the recommended type for auto-incremented database ids.
|
||||
/// Use nil for players that are not inserted yet in the database.
|
||||
var id: Int64?
|
||||
var book: String
|
||||
var scrollOffset: Int
|
||||
}
|
||||
|
||||
extension Ribbon {
|
||||
}
|
||||
|
||||
// MARK: - Persistence
|
||||
|
||||
/// Make Line a Codable Record.
|
||||
///
|
||||
/// See <https://github.com/groue/GRDB.swift/blob/master/README.md#records>
|
||||
extension Ribbon: Codable, FetchableRecord, MutablePersistableRecord {
|
||||
// Define database columns from CodingKeys
|
||||
fileprivate enum Columns {
|
||||
static let id = Column(CodingKeys.id)
|
||||
static let book = Column(CodingKeys.book)
|
||||
static let scrollOffset = Column(CodingKeys.scrollOffset)
|
||||
}
|
||||
|
||||
/// Updates a player id after it has been inserted in the database.
|
||||
mutating func didInsert(_ inserted: InsertionSuccess) {
|
||||
id = inserted.rowID
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Line Database Requests
|
||||
|
||||
/// Define some player requests used by the application.
|
||||
///
|
||||
/// See <https://github.com/groue/GRDB.swift/blob/master/README.md#requests>
|
||||
/// See <https://github.com/groue/GRDB.swift/blob/master/Documentation/GoodPracticesForDesigningRecordTypes.md>
|
||||
extension DerivableRequest<Line> {
|
||||
/// A request of players ordered by name.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// let players: [Line] = try dbWriter.read { db in
|
||||
/// try Line.all().orderedByName().fetchAll(db)
|
||||
/// }
|
||||
//func orderedByName() -> Self {
|
||||
// // Sort by name in a localized case insensitive fashion
|
||||
// // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
|
||||
// order(Line.Columns.name.collating(.localizedCaseInsensitiveCompare))
|
||||
//}
|
||||
|
||||
///// A request of players ordered by score.
|
||||
/////
|
||||
///// For example:
|
||||
/////
|
||||
///// let players: [Line] = try dbWriter.read { db in
|
||||
///// try Line.all().orderedByScore().fetchAll(db)
|
||||
///// }
|
||||
///// let bestLine: Line? = try dbWriter.read { db in
|
||||
///// try Line.all().orderedByScore().fetchOne(db)
|
||||
///// }
|
||||
//func orderedByScore() -> Self {
|
||||
// // Sort by descending score, and then by name, in a
|
||||
// // localized case insensitive fashion
|
||||
// // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
|
||||
// order(
|
||||
// Line.Columns.score.desc,
|
||||
// Line.Columns.name.collating(.localizedCaseInsensitiveCompare))
|
||||
//}
|
||||
}
|
||||
|
||||
// Ribbon.swift
|
||||
// gloss
|
||||
//
|
||||
// Created by Saint on 2/27/23.
|
||||
//
|
||||
|
||||
import Foundation
|
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// Seg.swift
|
||||
// gloss
|
||||
//
|
||||
// Created by Saint on 2/24/23.
|
||||
//
|
||||
|
||||
import GRDB
|
||||
/// The Line struct.
|
||||
///
|
||||
/// Identifiable conformance supports SwiftUI list animations, and type-safe
|
||||
/// GRDB primary key methods.
|
||||
/// Equatable conformance supports tests.
|
||||
struct Seg: Identifiable, Equatable {
|
||||
/// The player id.
|
||||
///
|
||||
/// Int64 is the recommended type for auto-incremented database ids.
|
||||
/// Use nil for players that are not inserted yet in the database.
|
||||
var id: Int64?
|
||||
var rowids: String
|
||||
var book: String
|
||||
}
|
||||
|
||||
extension Seg {
|
||||
}
|
||||
|
||||
// MARK: - Persistence
|
||||
|
||||
/// Make Line a Codable Record.
|
||||
///
|
||||
/// See <https://github.com/groue/GRDB.swift/blob/master/README.md#records>
|
||||
extension Seg: Codable, FetchableRecord, MutablePersistableRecord {
|
||||
// Define database columns from CodingKeys
|
||||
fileprivate enum Columns {
|
||||
static let id = Column(CodingKeys.id)
|
||||
static let rowids = Column(CodingKeys.rowids)
|
||||
static let book = Column(CodingKeys.book)
|
||||
}
|
||||
|
||||
/// Updates a player id after it has been inserted in the database.
|
||||
mutating func didInsert(_ inserted: InsertionSuccess) {
|
||||
id = inserted.rowID
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Line Database Requests
|
||||
|
||||
/// Define some player requests used by the application.
|
||||
///
|
||||
/// See <https://github.com/groue/GRDB.swift/blob/master/README.md#requests>
|
||||
/// See <https://github.com/groue/GRDB.swift/blob/master/Documentation/GoodPracticesForDesigningRecordTypes.md>
|
||||
extension DerivableRequest<Line> {
|
||||
/// A request of players ordered by name.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// let players: [Line] = try dbWriter.read { db in
|
||||
/// try Line.all().orderedByName().fetchAll(db)
|
||||
/// }
|
||||
//func orderedByName() -> Self {
|
||||
// // Sort by name in a localized case insensitive fashion
|
||||
// // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
|
||||
// order(Line.Columns.name.collating(.localizedCaseInsensitiveCompare))
|
||||
//}
|
||||
|
||||
///// A request of players ordered by score.
|
||||
/////
|
||||
///// For example:
|
||||
/////
|
||||
///// let players: [Line] = try dbWriter.read { db in
|
||||
///// try Line.all().orderedByScore().fetchAll(db)
|
||||
///// }
|
||||
///// let bestLine: Line? = try dbWriter.read { db in
|
||||
///// try Line.all().orderedByScore().fetchOne(db)
|
||||
///// }
|
||||
//func orderedByScore() -> Self {
|
||||
// // Sort by descending score, and then by name, in a
|
||||
// // localized case insensitive fashion
|
||||
// // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
|
||||
// order(
|
||||
// Line.Columns.score.desc,
|
||||
// Line.Columns.name.collating(.localizedCaseInsensitiveCompare))
|
||||
//}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// SwiftUIView.swift
|
||||
// gloss
|
||||
//
|
||||
// Created by Saint on 10/23/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SwiftUIView: View {
|
||||
var body: some View {
|
||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
||||
}
|
||||
}
|
||||
|
||||
struct SwiftUIView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SwiftUIView()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.authentication-services.autofill-credential-provider</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>gloss.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1" systemVersion="11A491" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="false" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Item" representedClassName="Item" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="timestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Item" positionX="-63" positionY="-18" width="128" height="44"/>
|
||||
</elements>
|
||||
</model>
|
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// glossApp.swift
|
||||
// gloss
|
||||
//
|
||||
// Created by Saint on 10/23/22.
|
||||
//
|
||||
|
||||
import GRDBQuery
|
||||
import SwiftUI
|
||||
@main
|
||||
struct glossApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
.environment(\.appDatabase, .shared)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Give SwiftUI access to the database
|
||||
//
|
||||
// Define a new environment key that grants access to an AppDatabase.
|
||||
//
|
||||
// The technique is documented at
|
||||
// <https://developer.apple.com/documentation/swiftui/environmentkey>.
|
||||
|
||||
private struct AppDatabaseKey: EnvironmentKey {
|
||||
static var defaultValue: AppDatabase { .empty() }
|
||||
}
|
||||
|
||||
extension EnvironmentValues {
|
||||
var appDatabase: AppDatabase {
|
||||
get { self[AppDatabaseKey.self] }
|
||||
set { self[AppDatabaseKey.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
// In this demo app, views observe the database with the @Query property
|
||||
// wrapper, defined in the GRDBQuery package. Its documentation recommends to
|
||||
// define a dedicated initializer for `appDatabase` access, so we comply:
|
||||
|
||||
extension Query where Request.DatabaseContext == AppDatabase {
|
||||
/// Convenience initializer for requests that feed from `AppDatabase`.
|
||||
init(_ request: Request) {
|
||||
self.init(request, in: \.appDatabase)
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue