Close Menu
geekfence.comgeekfence.com
    What's Hot

    Designing trust & safety (T&S) in customer experience management (CXM): why T&S is becoming core to CXM operating model 

    January 24, 2026

    iPhone 18 Series Could Finally Bring Back Touch ID

    January 24, 2026

    The Visual Haystacks Benchmark! – The Berkeley Artificial Intelligence Research Blog

    January 24, 2026
    Facebook X (Twitter) Instagram
    • About Us
    • Contact Us
    Facebook Instagram
    geekfence.comgeekfence.com
    • Home
    • UK Tech News
    • AI
    • Big Data
    • Cyber Security
      • Cloud Computing
      • iOS Development
    • IoT
    • Mobile
    • Software
      • Software Development
      • Software Engineering
    • Technology
      • Green Technology
      • Nanotechnology
    • Telecom
    geekfence.comgeekfence.com
    Home»iOS Development»Problems with keyboard responsiveness in edit sheet
    iOS Development

    Problems with keyboard responsiveness in edit sheet

    AdminBy AdminJanuary 15, 2026No Comments9 Mins Read1 Views
    Facebook Twitter Pinterest LinkedIn Telegram Tumblr Email
    Problems with keyboard responsiveness in edit sheet
    Share
    Facebook Twitter LinkedIn Pinterest Email


    I am building an app in Swift UI and Swift 6 for iOS 26. I am using SwiftData to store content. I have an edit sheet which I am struggling with.

    When the sheet loads, the user cannot tap the TextField to edit immediately; it takes multiple taps for the keyboard to appear, and because of that, the text in the field is highlighted for cut, copy, and paste. I have been at this for days, even using AI to see if it can help, and I am no further forward.

    My EditPolicyView.swift code:

    //
    //  EditPolicyView.swift
    //  Policy Pal
    //
    //  Created by Justin Erswell on 09/01/2026.
    //
    
    import SwiftUI
    import SwiftData
    import PhotosUI
    
    // Lightweight attachment summary - no binary data, just metadata for display
    struct AttachmentSummary: Identifiable, Sendable {
        let id: UUID
        let filename: String
        let mimeType: String
        let isExisting: Bool  // true = already saved in SwiftData, false = newly added
    
        var isPDF: Bool { mimeType == "application/pdf" }
    
        // Init for existing attachments (extracted values, not the model itself)
        init(id: UUID, filename: String, mimeType: String, isExisting: Bool) {
            self.id = id
            self.filename = filename
            self.mimeType = mimeType
            self.isExisting = isExisting
        }
    
        // Convenience init for new attachments
        init(id: UUID = UUID(), filename: String, mimeType: String) {
            self.id = id
            self.filename = filename
            self.mimeType = mimeType
            self.isExisting = false
        }
    }
    
    // Simple value struct to pass data without SwiftData observation
    // NOTE: Attachments are NOT copied here to avoid blocking main thread with large binary data
    struct EditPolicyData: Identifiable {
        let id: PersistentIdentifier
        var name: String
        var category: PolicyCategory
        var provider: String
        var policyNumber: String
        var cost: Decimal
        var costFrequency: CostFrequency
        var renewalDate: Date
        var notes: String
        var reminderThirtyDays: Bool
        var reminderFourteenDays: Bool
        var reminderThreeDays: Bool
        var reminderRenewalDay: Bool
    
        init(from policy: PolicyItem) {
            let start = CFAbsoluteTimeGetCurrent()
            self.id = policy.persistentModelID
            print("⏱️ EditPolicyData: persistentModelID took \(CFAbsoluteTimeGetCurrent() - start)s")
    
            let t1 = CFAbsoluteTimeGetCurrent()
            self.name = policy.name
            self.category = policy.category
            self.provider = policy.provider
            self.policyNumber = policy.policyNumber
            self.cost = policy.cost
            self.costFrequency = policy.costFrequency
            self.renewalDate = policy.renewalDate
            self.notes = policy.notes
            print("⏱️ EditPolicyData: basic props took \(CFAbsoluteTimeGetCurrent() - t1)s")
    
            let t2 = CFAbsoluteTimeGetCurrent()
            let schedule = policy.reminderSchedule
            self.reminderThirtyDays = schedule.thirtyDays
            self.reminderFourteenDays = schedule.fourteenDays
            self.reminderThreeDays = schedule.threeDays
            self.reminderRenewalDay = schedule.renewalDay
            print("⏱️ EditPolicyData: reminderSchedule took \(CFAbsoluteTimeGetCurrent() - t2)s")
            print("⏱️ EditPolicyData: TOTAL took \(CFAbsoluteTimeGetCurrent() - start)s")
        }
    }
    
    // Wrapper view that passes data to the actual form
    struct EditPolicyView: View {
        let data: EditPolicyData
    
        var body: some View {
            EditPolicyFormView(
                policyID: data.id,
                initialName: data.name,
                initialCategory: data.category,
                initialProvider: data.provider,
                initialPolicyNumber: data.policyNumber,
                initialCost: data.cost,
                initialCostFrequency: data.costFrequency,
                initialRenewalDate: data.renewalDate,
                initialNotes: data.notes,
                initialReminderThirtyDays: data.reminderThirtyDays,
                initialReminderFourteenDays: data.reminderFourteenDays,
                initialReminderThreeDays: data.reminderThreeDays,
                initialReminderRenewalDay: data.reminderRenewalDay
            )
        }
    
        // Convenience init
        init(data: EditPolicyData) {
            self.data = data
        }
    
        init(policy: PolicyItem) {
            self.data = EditPolicyData(from: policy)
        }
    }
    
    // Actual form view with inline @State initialization (like AddPolicyView)
    struct EditPolicyFormView: View {
        @Environment(\.dismiss) private var dismiss
        @Environment(\.modelContext) private var modelContext
        @EnvironmentObject private var appSettings: AppSettings
    
        // Store the policy ID for saving
        let policyID: PersistentIdentifier
    
        // Initial values passed in
        let initialName: String
        let initialCategory: PolicyCategory
        let initialProvider: String
        let initialPolicyNumber: String
        let initialCost: Decimal
        let initialCostFrequency: CostFrequency
        let initialRenewalDate: Date
        let initialNotes: String
        let initialReminderThirtyDays: Bool
        let initialReminderFourteenDays: Bool
        let initialReminderThreeDays: Bool
        let initialReminderRenewalDay: Bool
    
        // Form state - using inline initialization like AddPolicyView
        @State private var name = ""
        @State private var category: PolicyCategory = .insurance
        @State private var provider = ""
        @State private var policyNumber = ""
        @State private var cost: Decimal = 0
        @State private var costString = ""
        @State private var costFrequency: CostFrequency = .yearly
        @State private var renewalDate = Date()
        @State private var notes = ""
    
        // Reminder schedule
        @State private var reminderThirtyDays = true
        @State private var reminderFourteenDays = true
        @State private var reminderThreeDays = true
        @State private var reminderRenewalDay = true
    
        // Track if we've loaded initial values
        @State private var hasLoadedInitialValues = false
    
        // Attachments - use lightweight summaries for display, track changes separately
        @State private var attachmentSummaries: [AttachmentSummary] = []
        @State private var newAttachments: [Attachment] = []  // Newly added attachments (with data)
        @State private var deletedAttachmentIDs: Set<UUID> = []  // IDs of existing attachments to delete
        @State private var attachmentsLoaded = false
        @State private var selectedPhotoItems: [PhotosPickerItem] = []
        @State private var showingDocumentScanner = false
        @State private var showingFilePicker = false
    
        @State private var showingValidationError = false
        @State private var validationErrorMessage = ""
    
        // MARK: - Subscription-specific Labels
        private var isSubscription: Bool {
            category == .subscription
        }
    
        private var nameFieldLabel: String {
            isSubscription ? "Subscription Name" : "Name"
        }
    
        private var providerFieldLabel: String {
            isSubscription ? "Service" : "Provider"
        }
    
        private var referenceFieldLabel: String {
            isSubscription ? "Account ID (optional)" : "Reference Number"
        }
    
        private var dateFieldLabel: String {
            isSubscription ? "Next Billing Date" : "Renewal Date"
        }
    
        private var basicInfoSectionHeader: String {
            isSubscription ? "Subscription Details" : "Basic Information"
        }
    
        private var dateSectionHeader: String {
            isSubscription ? "Billing" : "Renewal"
        }
    
        private var reminderFooterText: String {
            isSubscription
                ? "You'll receive notifications at 9:00 AM before your billing date."
                : "You'll receive notifications at 9:00 AM on these days."
        }
    
        var body: some View {
            // Match AddPolicyView structure exactly
            NavigationStack {
                Form {
                    // Basic Info Section - minimal test
                    Section {
                        TextField(nameFieldLabel, text: $name)
                    } header: {
                        Text(basicInfoSectionHeader)
                    }
                }
                .navigationTitle("Edit Record")
                .navigationBarTitleDisplayMode(.inline)
                .toolbar {
                    ToolbarItem(placement: .cancellationAction) {
                        Button("Cancel") {
                            dismiss()
                        }
                    }
                    ToolbarItem(placement: .confirmationAction) {
                        Button("Save") {
                            saveChanges()
                        }
                        .disabled(name.isEmpty)
                    }
                }
                .alert("Validation Error", isPresented: $showingValidationError) {
                    Button("OK") { }
                } message: {
                    Text(validationErrorMessage)
                }
                .onAppear {
                    // Load initial values only once
                    if !hasLoadedInitialValues {
                        name = initialName
                        category = initialCategory
                        provider = initialProvider
                        policyNumber = initialPolicyNumber
                        cost = initialCost
                        costString = "\(initialCost)"
                        costFrequency = initialCostFrequency
                        renewalDate = initialRenewalDate
                        notes = initialNotes
                        reminderThirtyDays = initialReminderThirtyDays
                        reminderFourteenDays = initialReminderFourteenDays
                        reminderThreeDays = initialReminderThreeDays
                        reminderRenewalDay = initialReminderRenewalDay
                        hasLoadedInitialValues = true
                    }
                }
            }
            /* TEMPORARILY DISABLED - restore after keyboard test
            .sheet(isPresented: $showingDocumentScanner) {
                DocumentScannerView { images in
                    processScannedImages(images)
                }
            }
            .sheet(isPresented: $showingFilePicker) {
                DocumentPickerView { urls in
                    processSelectedFiles(urls)
                }
            }
            .onChange(of: selectedPhotoItems) { _, newItems in
                processSelectedPhotos(newItems)
            }
            .task {
                // Load attachments in background to avoid blocking UI
                await loadAttachments()
            }
            */
        }
    
        // Load attachment METADATA only (not binary data) to avoid blocking main thread
        private func loadAttachments() async {
            guard !attachmentsLoaded else { return }
            let start = CFAbsoluteTimeGetCurrent()
            print("⏱️ loadAttachments: starting...")
    
            // Use a background context to avoid blocking main thread
            let container = modelContext.container
            let policyIDCopy = policyID
    
            // Fetch raw metadata as tuples (Sendable) from background
            let metadata: [(UUID, String, String)] = await Task.detached {
                let bgStart = CFAbsoluteTimeGetCurrent()
                let backgroundContext = ModelContext(container)
                guard let policy = backgroundContext.model(for: policyIDCopy) as? PolicyItem else {
                    return []
                }
                // Only access metadata properties, NOT the data property
                let result = policy.safeAttachments.map { ($0.id, $0.filename, $0.mimeType) }
                print("⏱️ loadAttachments background task took \(CFAbsoluteTimeGetCurrent() - bgStart)s")
                return result
            }.value
    
            // Create summaries on main actor
            attachmentSummaries = metadata.map {
                AttachmentSummary(id: $0.0, filename: $0.1, mimeType: $0.2, isExisting: true)
            }
            attachmentsLoaded = true
            print("⏱️ loadAttachments: TOTAL took \(CFAbsoluteTimeGetCurrent() - start)s")
        }
    
        // MARK: - Save Changes
        private func saveChanges() {
            guard !name.trimmingCharacters(in: .whitespaces).isEmpty else {
                validationErrorMessage = "Please enter a name."
                showingValidationError = true
                return
            }
    
            // Fetch the policy by ID
            guard let policy = modelContext.model(for: policyID) as? PolicyItem else {
                validationErrorMessage = "Could not find record to update."
                showingValidationError = true
                return
            }
    
            policy.name = name.trimmingCharacters(in: .whitespaces)
            policy.category = category
            policy.provider = provider.trimmingCharacters(in: .whitespaces)
            policy.policyNumber = policyNumber.trimmingCharacters(in: .whitespaces)
            policy.cost = cost
            policy.costFrequency = costFrequency
            policy.renewalDate = renewalDate
            policy.notes = notes.trimmingCharacters(in: .whitespaces)
            policy.updatedAt = Date()
    
            policy.reminderSchedule = ReminderSchedule(
                thirtyDays: reminderThirtyDays,
                fourteenDays: reminderFourteenDays,
                threeDays: reminderThreeDays,
                renewalDay: reminderRenewalDay
            )
    
            // Only modify attachments that changed (not rewriting everything)
            // 1. Remove deleted attachments
            if !deletedAttachmentIDs.isEmpty {
                policy.safeAttachments.removeAll { deletedAttachmentIDs.contains($0.id) }
            }
    
            // 2. Add new attachments
            for attachment in newAttachments {
                policy.safeAttachments.append(attachment)
            }
    
            // Reschedule notifications
            Task {
                await NotificationManager.shared.scheduleNotifications(for: policy)
            }
    
            dismiss()
        }
    
        // MARK: - Attachment Handling
        private func removeAttachment(_ summary: AttachmentSummary) {
            attachmentSummaries.removeAll { $0.id == summary.id }
            if summary.isExisting {
                // Mark existing attachment for deletion on save
                deletedAttachmentIDs.insert(summary.id)
            } else {
                // Remove newly added attachment
                newAttachments.removeAll { $0.id == summary.id }
            }
        }
    
        private func processScannedImages(_ images: [UIImage]) {
            for (index, image) in images.enumerated() {
                if let data = image.jpegData(compressionQuality: 0.8) {
                    let id = UUID()
                    let filename = "scan_\(attachmentSummaries.count + index + 1).jpg"
                    let mimeType = "image/jpeg"
    
                    // Add to newAttachments (with data) for saving
                    let attachment = Attachment(filename: filename, data: data, mimeType: mimeType)
                    attachment.id = id
                    newAttachments.append(attachment)
    
                    // Add summary for display
                    attachmentSummaries.append(AttachmentSummary(id: id, filename: filename, mimeType: mimeType))
                }
            }
        }
    
        private func processSelectedPhotos(_ items: [PhotosPickerItem]) {
            for item in items {
                Task {
                    if let data = try? await item.loadTransferable(type: Data.self) {
                        await MainActor.run {
                            let id = UUID()
                            let filename = "photo_\(attachmentSummaries.count + 1).jpg"
                            let mimeType = "image/jpeg"
    
                            // Add to newAttachments (with data) for saving
                            let attachment = Attachment(filename: filename, data: data, mimeType: mimeType)
                            attachment.id = id
                            newAttachments.append(attachment)
    
                            // Add summary for display
                            attachmentSummaries.append(AttachmentSummary(id: id, filename: filename, mimeType: mimeType))
                        }
                    }
                }
            }
            selectedPhotoItems = []
        }
    
        private func processSelectedFiles(_ urls: [URL]) {
            for url in urls {
                guard url.startAccessingSecurityScopedResource() else { continue }
                defer { url.stopAccessingSecurityScopedResource() }
    
                if let data = try? Data(contentsOf: url) {
                    let id = UUID()
                    let filename = url.lastPathComponent
                    let mimeType = url.pathExtension.lowercased() == "pdf" ? "application/pdf" : "image/jpeg"
    
                    // Add to newAttachments (with data) for saving
                    let attachment = Attachment(filename: filename, data: data, mimeType: mimeType)
                    attachment.id = id
                    newAttachments.append(attachment)
    
                    // Add summary for display
                    attachmentSummaries.append(AttachmentSummary(id: id, filename: filename, mimeType: mimeType))
                }
            }
        }
    }
    
    #Preview {
        EditPolicyView(policy: PolicyItem(
            name: "Test Policy",
            category: .insurance,
            provider: "Test Provider",
            renewalDate: Date()
        ))
        .modelContainer(for: PolicyItem.self, inMemory: true)
        .environmentObject(AppSettings.shared)
    }
    

    Also A screenshot of the view running on an iPhone 17 Pro Max: Problems with keyboard responsiveness in edit sheet

    I am sure I am doing something intensely stupid and would be grateful for help from the community on this.



    Source link

    Share. Facebook Twitter Pinterest LinkedIn Tumblr Email

    Related Posts

    A Deep Dive into SwiftData migrations – Donny Wals

    January 24, 2026

    AI, find me some work…

    January 23, 2026

    Swift adapter design pattern – The.Swift.Dev.

    January 22, 2026

    Text is not visible when the button is in disabled state

    January 21, 2026

    What’s New in SwiftUI for iOS 18

    January 19, 2026

    WWDC 2023: A Reflection on Apple’s “Spatial Computing” Journey

    January 17, 2026
    Top Posts

    Understanding U-Net Architecture in Deep Learning

    November 25, 202511 Views

    Hard-braking events as indicators of road segment crash risk

    January 14, 20269 Views

    Microsoft 365 Copilot now enables you to build apps and workflows

    October 29, 20258 Views
    Don't Miss

    Designing trust & safety (T&S) in customer experience management (CXM): why T&S is becoming core to CXM operating model 

    January 24, 2026

    Customer Experience (CX) now sits at the intersection of Artificial Intelligence (AI)-enabled automation, identity and access journeys, AI-generated content…

    iPhone 18 Series Could Finally Bring Back Touch ID

    January 24, 2026

    The Visual Haystacks Benchmark! – The Berkeley Artificial Intelligence Research Blog

    January 24, 2026

    Data and Analytics Leaders Think They’re AI-Ready. They’re Probably Not. 

    January 24, 2026
    Stay In Touch
    • Facebook
    • Instagram
    About Us

    At GeekFence, we are a team of tech-enthusiasts, industry watchers and content creators who believe that technology isn’t just about gadgets—it’s about how innovation transforms our lives, work and society. We’ve come together to build a place where readers, thinkers and industry insiders can converge to explore what’s next in tech.

    Our Picks

    Designing trust & safety (T&S) in customer experience management (CXM): why T&S is becoming core to CXM operating model 

    January 24, 2026

    iPhone 18 Series Could Finally Bring Back Touch ID

    January 24, 2026

    Subscribe to Updates

    Please enable JavaScript in your browser to complete this form.
    Loading
    • About Us
    • Contact Us
    • Disclaimer
    • Privacy Policy
    • Terms and Conditions
    © 2026 Geekfence.All Rigt Reserved.

    Type above and press Enter to search. Press Esc to cancel.