From d4f2d928b623906b68a41e802b703c18d63a5e8e Mon Sep 17 00:00:00 2001 From: oliverhnat Date: Mon, 1 Dec 2025 15:35:13 +0100 Subject: [PATCH] feat(worterbuch): basic app implementation --- WorterBuch/ContentView.swift | 69 +---- WorterBuch/DictionaryHelper.swift | 32 +++ WorterBuch/FieldEditorView.swift | 127 +++++++++ WorterBuch/HandwritingCanvasView.swift | 74 +++++ WorterBuch/HandwritingRecognizer.swift | 117 ++++++++ WorterBuch/Info.plist | 7 +- WorterBuch/Persistence.swift | 12 +- WorterBuch/VocabularyEntry+Extensions.swift | 61 +++++ WorterBuch/VocabularyFieldCell.swift | 61 +++++ WorterBuch/VocabularyGridView.swift | 255 ++++++++++++++++++ WorterBuch/WorterBuch.entitlements | 11 +- .../WorterBuch.xcdatamodel/contents | 11 +- 12 files changed, 746 insertions(+), 91 deletions(-) create mode 100644 WorterBuch/DictionaryHelper.swift create mode 100644 WorterBuch/FieldEditorView.swift create mode 100644 WorterBuch/HandwritingCanvasView.swift create mode 100644 WorterBuch/HandwritingRecognizer.swift create mode 100644 WorterBuch/VocabularyEntry+Extensions.swift create mode 100644 WorterBuch/VocabularyFieldCell.swift create mode 100644 WorterBuch/VocabularyGridView.swift diff --git a/WorterBuch/ContentView.swift b/WorterBuch/ContentView.swift index ff32469..402d3ab 100644 --- a/WorterBuch/ContentView.swift +++ b/WorterBuch/ContentView.swift @@ -9,78 +9,11 @@ import SwiftUI import CoreData struct ContentView: View { - @Environment(\.managedObjectContext) private var viewContext - - @FetchRequest( - sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)], - animation: .default) - private var items: FetchedResults - var body: some View { - NavigationView { - List { - ForEach(items) { item in - NavigationLink { - Text("Item at \(item.timestamp!, formatter: itemFormatter)") - } label: { - Text(item.timestamp!, formatter: itemFormatter) - } - } - .onDelete(perform: deleteItems) - } - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - EditButton() - } - ToolbarItem { - Button(action: addItem) { - Label("Add Item", systemImage: "plus") - } - } - } - Text("Select an item") - } - } - - private func addItem() { - withAnimation { - let newItem = Item(context: viewContext) - newItem.timestamp = Date() - - do { - try viewContext.save() - } catch { - // Replace this implementation with code to handle the error appropriately. - // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - let nsError = error as NSError - fatalError("Unresolved error \(nsError), \(nsError.userInfo)") - } - } - } - - private func deleteItems(offsets: IndexSet) { - withAnimation { - offsets.map { items[$0] }.forEach(viewContext.delete) - - do { - try viewContext.save() - } catch { - // Replace this implementation with code to handle the error appropriately. - // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - let nsError = error as NSError - fatalError("Unresolved error \(nsError), \(nsError.userInfo)") - } - } + VocabularyGridView() } } -private let itemFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateStyle = .short - formatter.timeStyle = .medium - return formatter -}() - #Preview { ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) } diff --git a/WorterBuch/DictionaryHelper.swift b/WorterBuch/DictionaryHelper.swift new file mode 100644 index 0000000..3a43cd5 --- /dev/null +++ b/WorterBuch/DictionaryHelper.swift @@ -0,0 +1,32 @@ +// +// DictionaryHelper.swift +// WorterBuch +// +// Created by Oliver Hnát on 01.12.2025. +// + +import UIKit + +class DictionaryHelper { + + static func showDefinition(for text: String) { + guard UIReferenceLibraryViewController.dictionaryHasDefinition(forTerm: text) else { + print("No definition found for: \(text)") + return + } + + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let rootViewController = windowScene.windows.first?.rootViewController else { + return + } + + let dictionaryViewController = UIReferenceLibraryViewController(term: text) + var topController = rootViewController + + while let presented = topController.presentedViewController { + topController = presented + } + + topController.present(dictionaryViewController, animated: true, completion: nil) + } +} diff --git a/WorterBuch/FieldEditorView.swift b/WorterBuch/FieldEditorView.swift new file mode 100644 index 0000000..681bdfc --- /dev/null +++ b/WorterBuch/FieldEditorView.swift @@ -0,0 +1,127 @@ +// +// FieldEditorView.swift +// WorterBuch +// +// Created by Oliver Hnát on 01.12.2025. +// + +import SwiftUI +import PencilKit + +enum FieldType { + case germanWord + case germanExplanation + case englishTranslation + + var title: String { + switch self { + case .germanWord: return "German Word" + case .germanExplanation: return "German Explanation" + case .englishTranslation: return "English Translation" + } + } +} + +struct FieldEditorView: View { + @Environment(\.dismiss) private var dismiss + @Binding var drawing: PKDrawing + @Binding var text: String + let fieldType: FieldType + + @State private var isRecognizing = false + + var body: some View { + NavigationView { + VStack(spacing: 0) { + // Handwriting canvas + HandwritingCanvasView( + drawing: $drawing, + onDrawingChanged: { newDrawing in + recognizeHandwriting(newDrawing) + }, + isEditable: true + ) + .frame(maxWidth: .infinity) + .frame(height: 400) + .background(Color(.systemGray6)) + .cornerRadius(12) + .padding() + + // Transcribed text section + VStack(alignment: .leading, spacing: 8) { + HStack { + Text("Transcribed Text") + .font(.headline) + .foregroundColor(.secondary) + + if isRecognizing { + ProgressView() + .scaleEffect(0.8) + } + + Spacer() + } + + TextEditor(text: $text) + .font(.body) + .frame(minHeight: 100) + .padding(8) + .background(Color(.systemGray6)) + .cornerRadius(8) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Color.gray.opacity(0.3), lineWidth: 1) + ) + } + .padding() + + Spacer() + } + .navigationTitle(fieldType.title) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button("Clear") { + drawing = PKDrawing() + text = "" + } + } + ToolbarItem(placement: .navigationBarTrailing) { + Button("Done") { + dismiss() + } + .fontWeight(.semibold) + } + } + } + .navigationViewStyle(.stack) + } + + private func recognizeHandwriting(_ drawing: PKDrawing) { + print("=== recognizeHandwriting called ===") + print("Drawing bounds: \(drawing.bounds)") + print("Drawing is empty: \(drawing.bounds.isEmpty)") + + guard !drawing.bounds.isEmpty else { + print("Drawing bounds are empty, skipping recognition") + return + } + + print("Starting recognition...") + isRecognizing = true + Task { + if let recognizedText = await HandwritingRecognizer.recognizeTextAsync(from: drawing) { + print("Recognition successful: \(recognizedText)") + await MainActor.run { + text = recognizedText + isRecognizing = false + } + } else { + print("Recognition returned nil") + await MainActor.run { + isRecognizing = false + } + } + } + } +} diff --git a/WorterBuch/HandwritingCanvasView.swift b/WorterBuch/HandwritingCanvasView.swift new file mode 100644 index 0000000..2a77a68 --- /dev/null +++ b/WorterBuch/HandwritingCanvasView.swift @@ -0,0 +1,74 @@ +// +// HandwritingCanvasView.swift +// WorterBuch +// +// Created by Oliver Hnát on 01.12.2025. +// + +import SwiftUI +import PencilKit + +struct HandwritingCanvasView: UIViewRepresentable { + @Binding var drawing: PKDrawing + var onDrawingChanged: ((PKDrawing) -> Void)? + var isEditable: Bool = true + + func makeUIView(context: Context) -> PKCanvasView { + let canvasView = PKCanvasView() + canvasView.drawing = drawing + canvasView.delegate = context.coordinator + canvasView.drawingPolicy = .anyInput + canvasView.isOpaque = false + canvasView.backgroundColor = .clear + canvasView.isUserInteractionEnabled = isEditable + + // Use the shared tool picker and show it after a delay + DispatchQueue.main.async { + if isEditable, let window = canvasView.window { + let toolPicker = PKToolPicker.shared(for: window) + toolPicker?.setVisible(true, forFirstResponder: canvasView) + toolPicker?.addObserver(canvasView) + canvasView.becomeFirstResponder() + } + } + + return canvasView + } + + func updateUIView(_ canvasView: PKCanvasView, context: Context) { + if canvasView.drawing != drawing { + canvasView.drawing = drawing + } + canvasView.isUserInteractionEnabled = isEditable + + // Ensure tool picker is visible when editable + if isEditable { + DispatchQueue.main.async { + if let window = canvasView.window { + let toolPicker = PKToolPicker.shared(for: window) + toolPicker?.setVisible(true, forFirstResponder: canvasView) + canvasView.becomeFirstResponder() + } + } + } + } + + func makeCoordinator() -> Coordinator { + Coordinator(drawing: $drawing, onDrawingChanged: onDrawingChanged) + } + + class Coordinator: NSObject, PKCanvasViewDelegate { + var drawing: Binding + var onDrawingChanged: ((PKDrawing) -> Void)? + + init(drawing: Binding, onDrawingChanged: ((PKDrawing) -> Void)?) { + self.drawing = drawing + self.onDrawingChanged = onDrawingChanged + } + + func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) { + drawing.wrappedValue = canvasView.drawing + onDrawingChanged?(canvasView.drawing) + } + } +} diff --git a/WorterBuch/HandwritingRecognizer.swift b/WorterBuch/HandwritingRecognizer.swift new file mode 100644 index 0000000..01ea40b --- /dev/null +++ b/WorterBuch/HandwritingRecognizer.swift @@ -0,0 +1,117 @@ +// +// HandwritingRecognizer.swift +// WorterBuch +// +// Created by Oliver Hnát on 01.12.2025. +// + +import Foundation +import PencilKit +import Vision +import UIKit + +class HandwritingRecognizer { + + static func recognizeText(from drawing: PKDrawing, completion: @escaping (String?) -> Void) { + // Check if drawing has content + guard !drawing.bounds.isEmpty else { + print("Drawing bounds are empty") + completion(nil) + return + } + + // Convert drawing to image with high scale for better recognition + let bounds = drawing.bounds + print("Drawing bounds: \(bounds)") + + // Create a high-resolution image with white background for better recognition + let scale: CGFloat = 4.0 + let imageSize = CGSize(width: bounds.width * scale, height: bounds.height * scale) + + // Create image with white background + UIGraphicsBeginImageContextWithOptions(bounds.size, true, scale) + guard let context = UIGraphicsGetCurrentContext() else { + print("Failed to create graphics context") + completion(nil) + return + } + + // Fill with white background + context.setFillColor(UIColor.white.cgColor) + context.fill(CGRect(origin: .zero, size: bounds.size)) + + // Draw the PKDrawing + let drawingImage = drawing.image(from: bounds, scale: scale) + drawingImage.draw(at: .zero) + + guard let image = UIGraphicsGetImageFromCurrentImageContext() else { + UIGraphicsEndImageContext() + print("Failed to create image from context") + completion(nil) + return + } + UIGraphicsEndImageContext() + + guard let cgImage = image.cgImage else { + print("Failed to create CGImage from drawing") + completion(nil) + return + } + + print("Created image with size: \(image.size), scale: \(image.scale), pixel size: \(imageSize)") + + // Create Vision request + let request = VNRecognizeTextRequest { request, error in + if let error = error { + print("Text recognition error: \(error.localizedDescription)") + completion(nil) + return + } + + guard let observations = request.results as? [VNRecognizedTextObservation] else { + print("No text observations found") + completion(nil) + return + } + + print("Found \(observations.count) text observations") + + // Combine all recognized text + let recognizedStrings = observations.compactMap { observation in + observation.topCandidates(1).first?.string + } + + print("Recognized strings: \(recognizedStrings)") + + let recognizedText = recognizedStrings.joined(separator: " ") + completion(recognizedText.isEmpty ? nil : recognizedText) + } + + // Configure for German language and handwriting + request.recognitionLanguages = ["de-DE", "en-US"] + request.recognitionLevel = .accurate + request.usesLanguageCorrection = true + + // Enable automatic language detection as fallback + request.automaticallyDetectsLanguage = true + + // Perform request + let requestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:]) + DispatchQueue.global(qos: .userInitiated).async { + do { + try requestHandler.perform([request]) + } catch { + print("Failed to perform text recognition: \(error.localizedDescription)") + completion(nil) + } + } + } + + static func recognizeTextAsync(from drawing: PKDrawing) async -> String? { + await withCheckedContinuation { continuation in + recognizeText(from: drawing) { result in + continuation.resume(returning: result) + } + } + } +} diff --git a/WorterBuch/Info.plist b/WorterBuch/Info.plist index ca9a074..0c67376 100644 --- a/WorterBuch/Info.plist +++ b/WorterBuch/Info.plist @@ -1,10 +1,5 @@ - - UIBackgroundModes - - remote-notification - - + diff --git a/WorterBuch/Persistence.swift b/WorterBuch/Persistence.swift index 52f46c5..a2b86c9 100644 --- a/WorterBuch/Persistence.swift +++ b/WorterBuch/Persistence.swift @@ -14,15 +14,17 @@ struct PersistenceController { static let preview: PersistenceController = { let result = PersistenceController(inMemory: true) let viewContext = result.container.viewContext - for _ in 0..<10 { - let newItem = Item(context: viewContext) - newItem.timestamp = Date() + for i in 0..<5 { + let entry = VocabularyEntry.create(in: viewContext) + entry.timestamp = Date() + entry.id = UUID() + entry.germanWordText = "Beispielwort \(i + 1)" + entry.germanExplanationText = "Dies ist eine Erklärung des deutschen Wortes" + entry.englishTranslationText = "Example word \(i + 1)" } do { try viewContext.save() } catch { - // Replace this implementation with code to handle the error appropriately. - // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. let nsError = error as NSError fatalError("Unresolved error \(nsError), \(nsError.userInfo)") } diff --git a/WorterBuch/VocabularyEntry+Extensions.swift b/WorterBuch/VocabularyEntry+Extensions.swift new file mode 100644 index 0000000..98a5a2f --- /dev/null +++ b/WorterBuch/VocabularyEntry+Extensions.swift @@ -0,0 +1,61 @@ +// +// VocabularyEntry+Extensions.swift +// WorterBuch +// +// Created by Oliver Hnát on 01.12.2025. +// + +import Foundation +import PencilKit +import CoreData + +extension VocabularyEntry { + + // MARK: - German Word Drawing + + var germanWordPKDrawing: PKDrawing { + get { + guard let data = germanWordDrawing else { return PKDrawing() } + return (try? PKDrawing(data: data)) ?? PKDrawing() + } + set { + germanWordDrawing = newValue.dataRepresentation() + } + } + + // MARK: - German Explanation Drawing + + var germanExplanationPKDrawing: PKDrawing { + get { + guard let data = germanExplanationDrawing else { return PKDrawing() } + return (try? PKDrawing(data: data)) ?? PKDrawing() + } + set { + germanExplanationDrawing = newValue.dataRepresentation() + } + } + + // MARK: - English Translation Drawing + + var englishTranslationPKDrawing: PKDrawing { + get { + guard let data = englishTranslationDrawing else { return PKDrawing() } + return (try? PKDrawing(data: data)) ?? PKDrawing() + } + set { + englishTranslationDrawing = newValue.dataRepresentation() + } + } + + // MARK: - Convenience Initializer + + static func create(in context: NSManagedObjectContext) -> VocabularyEntry { + let entry = VocabularyEntry(context: context) + entry.id = UUID() + entry.timestamp = Date() + entry.germanWordText = "" + entry.germanExplanationText = "" + entry.englishTranslationText = "" + return entry + } +} diff --git a/WorterBuch/VocabularyFieldCell.swift b/WorterBuch/VocabularyFieldCell.swift new file mode 100644 index 0000000..c2cf96b --- /dev/null +++ b/WorterBuch/VocabularyFieldCell.swift @@ -0,0 +1,61 @@ +// +// VocabularyFieldCell.swift +// WorterBuch +// +// Created by Oliver Hnát on 01.12.2025. +// + +import SwiftUI +import PencilKit + +struct VocabularyFieldCell: View { + let drawing: PKDrawing + let text: String + let onTap: () -> Void + let onLongPress: () -> Void + + var body: some View { + VStack(alignment: .leading, spacing: 4) { + // Handwriting preview + if !drawing.bounds.isEmpty { + Image(uiImage: drawing.image(from: drawing.bounds, scale: 2.0)) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 60) + .frame(maxWidth: .infinity) + .background(Color(.systemGray6)) + } else { + Rectangle() + .fill(Color(.systemGray6)) + .frame(height: 60) + .overlay( + Text("Tap to write") + .font(.caption) + .foregroundColor(.secondary) + ) + } + + // Transcribed text + Text(text.isEmpty ? " " : text.lowercased()) + .font(.caption) + .foregroundColor(.primary) + .lineLimit(2) + .frame(maxWidth: .infinity, alignment: .leading) + .frame(height: 30) + } + .padding(8) + .background(Color(.systemBackground)) + .cornerRadius(8) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Color.gray.opacity(0.2), lineWidth: 1) + ) + .contentShape(Rectangle()) + .onTapGesture { + onTap() + } + .onLongPressGesture { + onLongPress() + } + } +} diff --git a/WorterBuch/VocabularyGridView.swift b/WorterBuch/VocabularyGridView.swift new file mode 100644 index 0000000..8181a7e --- /dev/null +++ b/WorterBuch/VocabularyGridView.swift @@ -0,0 +1,255 @@ +// +// VocabularyGridView.swift +// WorterBuch +// +// Created by Oliver Hnát on 01.12.2025. +// + +import SwiftUI +import PencilKit +import CoreData + +struct VocabularyGridView: View { + @Environment(\.managedObjectContext) private var viewContext + @FetchRequest( + sortDescriptors: [NSSortDescriptor(keyPath: \VocabularyEntry.timestamp, ascending: false)], + animation: .default + ) + private var entries: FetchedResults + + @State private var searchText = "" + @State private var selectedEntry: VocabularyEntry? + @State private var selectedFieldType: FieldType? + @State private var showingFieldEditor = false + + // Temporary bindings for editing + @State private var editingDrawing: PKDrawing = PKDrawing() + @State private var editingText: String = "" + + var filteredEntries: [VocabularyEntry] { + if searchText.isEmpty { + return Array(entries) + } else { + return entries.filter { entry in + (entry.germanWordText?.localizedCaseInsensitiveContains(searchText) ?? false) || + (entry.germanExplanationText?.localizedCaseInsensitiveContains(searchText) ?? false) || + (entry.englishTranslationText?.localizedCaseInsensitiveContains(searchText) ?? false) + } + } + } + + var body: some View { + NavigationView { + VStack(spacing: 0) { + // Search bar + HStack { + Image(systemName: "magnifyingglass") + .foregroundColor(.secondary) + TextField("Search words, explanations, or translations", text: $searchText) + .textFieldStyle(.plain) + if !searchText.isEmpty { + Button(action: { searchText = "" }) { + Image(systemName: "xmark.circle.fill") + .foregroundColor(.secondary) + } + } + } + .padding(10) + .background(Color(.systemGray6)) + .cornerRadius(10) + .padding() + + // Column headers + HStack(spacing: 12) { + Text("German Word") + .font(.headline) + .frame(maxWidth: .infinity, alignment: .leading) + + Text("German Explanation") + .font(.headline) + .frame(maxWidth: .infinity, alignment: .leading) + + Text("English Translation") + .font(.headline) + .frame(maxWidth: .infinity, alignment: .leading) + } + .padding(.horizontal) + .padding(.bottom, 8) + + Divider() + + // Entries list + ScrollView { + LazyVStack(spacing: 12) { + ForEach(filteredEntries, id: \.id) { entry in + VocabularyEntryRow( + entry: entry, + onSelectField: { fieldType in + openFieldEditor(for: entry, fieldType: fieldType) + }, + onLongPress: { fieldType in + showDefinition(for: entry, fieldType: fieldType) + } + ) + } + .onDelete(perform: deleteEntries) + } + .padding() + } + } + .navigationTitle("Wörterbuch") + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button(action: addEntry) { + Label("Add Entry", systemImage: "plus") + } + } + } + .sheet(isPresented: $showingFieldEditor) { + if let entry = selectedEntry, let fieldType = selectedFieldType { + FieldEditorView( + drawing: bindingForDrawing(entry: entry, fieldType: fieldType), + text: bindingForText(entry: entry, fieldType: fieldType), + fieldType: fieldType + ) + .onDisappear { + saveContext() + } + } + } + } + .navigationViewStyle(.stack) + } + + private func openFieldEditor(for entry: VocabularyEntry, fieldType: FieldType) { + selectedEntry = entry + selectedFieldType = fieldType + showingFieldEditor = true + } + + private func bindingForDrawing(entry: VocabularyEntry, fieldType: FieldType) -> Binding { + Binding( + get: { + switch fieldType { + case .germanWord: + return entry.germanWordPKDrawing + case .germanExplanation: + return entry.germanExplanationPKDrawing + case .englishTranslation: + return entry.englishTranslationPKDrawing + } + }, + set: { newValue in + switch fieldType { + case .germanWord: + entry.germanWordPKDrawing = newValue + case .germanExplanation: + entry.germanExplanationPKDrawing = newValue + case .englishTranslation: + entry.englishTranslationPKDrawing = newValue + } + } + ) + } + + private func bindingForText(entry: VocabularyEntry, fieldType: FieldType) -> Binding { + Binding( + get: { + switch fieldType { + case .germanWord: + return entry.germanWordText ?? "" + case .germanExplanation: + return entry.germanExplanationText ?? "" + case .englishTranslation: + return entry.englishTranslationText ?? "" + } + }, + set: { newValue in + switch fieldType { + case .germanWord: + entry.germanWordText = newValue + case .germanExplanation: + entry.germanExplanationText = newValue + case .englishTranslation: + entry.englishTranslationText = newValue + } + } + ) + } + + private func addEntry() { + withAnimation { + let _ = VocabularyEntry.create(in: viewContext) + saveContext() + } + } + + private func deleteEntries(offsets: IndexSet) { + withAnimation { + offsets.map { filteredEntries[$0] }.forEach(viewContext.delete) + saveContext() + } + } + + private func saveContext() { + do { + try viewContext.save() + } catch { + let nsError = error as NSError + print("Error saving context: \(nsError), \(nsError.userInfo)") + } + } + + private func showDefinition(for entry: VocabularyEntry, fieldType: FieldType) { + let text: String + switch fieldType { + case .germanWord: + text = entry.germanWordText ?? "" + case .germanExplanation: + text = entry.germanExplanationText ?? "" + case .englishTranslation: + text = entry.englishTranslationText ?? "" + } + + if !text.isEmpty { + DictionaryHelper.showDefinition(for: text) + } + } +} + +struct VocabularyEntryRow: View { + @ObservedObject var entry: VocabularyEntry + let onSelectField: (FieldType) -> Void + let onLongPress: (FieldType) -> Void + + var body: some View { + HStack(spacing: 12) { + // German word + VocabularyFieldCell( + drawing: entry.germanWordPKDrawing, + text: entry.germanWordText ?? "", + onTap: { onSelectField(.germanWord) }, + onLongPress: { onLongPress(.germanWord) } + ) + .frame(maxWidth: .infinity) + + // German explanation + VocabularyFieldCell( + drawing: entry.germanExplanationPKDrawing, + text: entry.germanExplanationText ?? "", + onTap: { onSelectField(.germanExplanation) }, + onLongPress: { onLongPress(.germanExplanation) } + ) + .frame(maxWidth: .infinity) + + // English translation + VocabularyFieldCell( + drawing: entry.englishTranslationPKDrawing, + text: entry.englishTranslationText ?? "", + onTap: { onSelectField(.englishTranslation) }, + onLongPress: { onLongPress(.englishTranslation) } + ) + .frame(maxWidth: .infinity) + } + } +} diff --git a/WorterBuch/WorterBuch.entitlements b/WorterBuch/WorterBuch.entitlements index 9e0940e..0c67376 100644 --- a/WorterBuch/WorterBuch.entitlements +++ b/WorterBuch/WorterBuch.entitlements @@ -1,14 +1,5 @@ - - aps-environment - development - com.apple.developer.icloud-container-identifiers - - com.apple.developer.icloud-services - - CloudKit - - + diff --git a/WorterBuch/WorterBuch.xcdatamodeld/WorterBuch.xcdatamodel/contents b/WorterBuch/WorterBuch.xcdatamodeld/WorterBuch.xcdatamodel/contents index e8d6ec8..51796ed 100644 --- a/WorterBuch/WorterBuch.xcdatamodeld/WorterBuch.xcdatamodel/contents +++ b/WorterBuch/WorterBuch.xcdatamodeld/WorterBuch.xcdatamodel/contents @@ -1,9 +1,16 @@ - + + + + + + + + - + \ No newline at end of file