feat(worterbuch): implement tap to select
This commit is contained in:
36
WorterBuch/SelectableTextView.swift
Normal file
36
WorterBuch/SelectableTextView.swift
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// SelectableTextView.swift
|
||||||
|
// WorterBuch
|
||||||
|
//
|
||||||
|
// Created by Oliver Hnát on 01.12.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
struct SelectableTextView: UIViewRepresentable {
|
||||||
|
let text: String
|
||||||
|
let font: UIFont
|
||||||
|
let textColor: UIColor
|
||||||
|
|
||||||
|
func makeUIView(context: Context) -> UITextView {
|
||||||
|
let textView = UITextView()
|
||||||
|
textView.isEditable = false
|
||||||
|
textView.isSelectable = true
|
||||||
|
textView.isScrollEnabled = false
|
||||||
|
textView.backgroundColor = .clear
|
||||||
|
textView.textContainerInset = .zero
|
||||||
|
textView.textContainer.lineFragmentPadding = 0
|
||||||
|
textView.textContainer.maximumNumberOfLines = 2
|
||||||
|
textView.textContainer.lineBreakMode = .byTruncatingTail
|
||||||
|
textView.dataDetectorTypes = []
|
||||||
|
|
||||||
|
return textView
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ textView: UITextView, context: Context) {
|
||||||
|
textView.text = text
|
||||||
|
textView.font = font
|
||||||
|
textView.textColor = textColor
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,11 +12,11 @@ struct VocabularyFieldCell: View {
|
|||||||
let drawing: PKDrawing
|
let drawing: PKDrawing
|
||||||
let text: String
|
let text: String
|
||||||
let onTap: () -> Void
|
let onTap: () -> Void
|
||||||
let onLongPress: () -> Void
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
// Handwriting preview
|
// Handwriting preview - tappable to edit
|
||||||
|
Group {
|
||||||
if !drawing.bounds.isEmpty {
|
if !drawing.bounds.isEmpty {
|
||||||
Image(uiImage: drawing.image(from: drawing.bounds, scale: 2.0))
|
Image(uiImage: drawing.image(from: drawing.bounds, scale: 2.0))
|
||||||
.resizable()
|
.resizable()
|
||||||
@@ -34,14 +34,27 @@ struct VocabularyFieldCell: View {
|
|||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture {
|
||||||
|
onTap()
|
||||||
|
}
|
||||||
|
|
||||||
// Transcribed text
|
// Transcribed text - selectable but not editable
|
||||||
Text(text.isEmpty ? " " : text.lowercased())
|
if !text.isEmpty {
|
||||||
.font(.body)
|
SelectableTextView(
|
||||||
.foregroundColor(.primary)
|
text: text.lowercased(),
|
||||||
.lineLimit(2)
|
font: .preferredFont(forTextStyle: .body),
|
||||||
|
textColor: .label
|
||||||
|
)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.frame(height: 40)
|
.frame(height: 40)
|
||||||
|
} else {
|
||||||
|
Text(" ")
|
||||||
|
.font(.body)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.frame(height: 40)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding(8)
|
.padding(8)
|
||||||
.background(Color(.systemBackground))
|
.background(Color(.systemBackground))
|
||||||
@@ -50,12 +63,5 @@ struct VocabularyFieldCell: View {
|
|||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: 8)
|
||||||
.stroke(Color.gray.opacity(0.2), lineWidth: 1)
|
.stroke(Color.gray.opacity(0.2), lineWidth: 1)
|
||||||
)
|
)
|
||||||
.contentShape(Rectangle())
|
|
||||||
.onTapGesture {
|
|
||||||
onTap()
|
|
||||||
}
|
|
||||||
.onLongPressGesture {
|
|
||||||
onLongPress()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,13 +85,11 @@ struct VocabularyGridView: View {
|
|||||||
entry: entry,
|
entry: entry,
|
||||||
onSelectField: { fieldType in
|
onSelectField: { fieldType in
|
||||||
openFieldEditor(for: entry, fieldType: fieldType)
|
openFieldEditor(for: entry, fieldType: fieldType)
|
||||||
},
|
|
||||||
onLongPress: { fieldType in
|
|
||||||
showDefinition(for: entry, fieldType: fieldType)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.listRowInsets(EdgeInsets(top: 6, leading: 12, bottom: 6, trailing: 12))
|
.listRowInsets(EdgeInsets(top: 6, leading: 12, bottom: 6, trailing: 12))
|
||||||
.listRowSeparator(.hidden)
|
.listRowSeparator(.hidden)
|
||||||
|
.listRowBackground(Color.clear)
|
||||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
deleteEntry(entry)
|
deleteEntry(entry)
|
||||||
@@ -102,6 +100,7 @@ struct VocabularyGridView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.listStyle(.plain)
|
.listStyle(.plain)
|
||||||
|
.environment(\.defaultMinListRowHeight, 0)
|
||||||
}
|
}
|
||||||
.navigationTitle("Wörterbuch")
|
.navigationTitle("Wörterbuch")
|
||||||
.toolbar {
|
.toolbar {
|
||||||
@@ -201,28 +200,11 @@ struct VocabularyGridView: View {
|
|||||||
print("Error saving context: \(nsError), \(nsError.userInfo)")
|
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 {
|
struct VocabularyEntryRow: View {
|
||||||
@ObservedObject var entry: VocabularyEntry
|
@ObservedObject var entry: VocabularyEntry
|
||||||
let onSelectField: (FieldType) -> Void
|
let onSelectField: (FieldType) -> Void
|
||||||
let onLongPress: (FieldType) -> Void
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
@@ -230,8 +212,7 @@ struct VocabularyEntryRow: View {
|
|||||||
VocabularyFieldCell(
|
VocabularyFieldCell(
|
||||||
drawing: entry.germanWordPKDrawing,
|
drawing: entry.germanWordPKDrawing,
|
||||||
text: entry.germanWordText ?? "",
|
text: entry.germanWordText ?? "",
|
||||||
onTap: { onSelectField(.germanWord) },
|
onTap: { onSelectField(.germanWord) }
|
||||||
onLongPress: { onLongPress(.germanWord) }
|
|
||||||
)
|
)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
@@ -239,8 +220,7 @@ struct VocabularyEntryRow: View {
|
|||||||
VocabularyFieldCell(
|
VocabularyFieldCell(
|
||||||
drawing: entry.germanExplanationPKDrawing,
|
drawing: entry.germanExplanationPKDrawing,
|
||||||
text: entry.germanExplanationText ?? "",
|
text: entry.germanExplanationText ?? "",
|
||||||
onTap: { onSelectField(.germanExplanation) },
|
onTap: { onSelectField(.germanExplanation) }
|
||||||
onLongPress: { onLongPress(.germanExplanation) }
|
|
||||||
)
|
)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
@@ -248,8 +228,7 @@ struct VocabularyEntryRow: View {
|
|||||||
VocabularyFieldCell(
|
VocabularyFieldCell(
|
||||||
drawing: entry.englishTranslationPKDrawing,
|
drawing: entry.englishTranslationPKDrawing,
|
||||||
text: entry.englishTranslationText ?? "",
|
text: entry.englishTranslationText ?? "",
|
||||||
onTap: { onSelectField(.englishTranslation) },
|
onTap: { onSelectField(.englishTranslation) }
|
||||||
onLongPress: { onLongPress(.englishTranslation) }
|
|
||||||
)
|
)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user