2

I'm creating a journaling app that would encourage the user to write at least a 100 characters in the Morning Pages section. I’d like to do that by placing a ProgressView at the bottom of a TextEditor. The progress bar would then go from 0 to 100% based on the user's input and stay at 100 if they write more than say 100 characters. The progress bar indicates the minimum chars needed to create the entry.

import SwiftUI
import Combine

struct AddNewEntryView: View {

@Environment(\.dismiss) var dismiss
@EnvironmentObject var data : JournalEntriesData
@State var typeOfNewEntry : EntryType
@State var newEntry : Entry
@State var progressValue: Double = 0.0
let MIN_ENTRY_CHARS = 10

let detector = PassthroughSubject<Void, Never>()
let publisher: AnyPublisher<Void, Never>

init(entryType: EntryType) {
    _typeOfNewEntry = State(initialValue: entryType)
    _newEntry = State(initialValue: Entry(type: entryType, text: ""))
    
    publisher = detector
        .debounce(for: .seconds(1), scheduler: DispatchQueue.main)
        .eraseToAnyPublisher()
}

func limitTo(num: Double, maxNum: Double) -> Double {
    return num > maxNum ? maxNum : num
}

private func updateProgressBar() {
    print("char count", newEntry.text.count)
    progressValue = Double(newEntry.text.count / MIN_ENTRY_CHARS)
}

var body: some View {
    VStack {
        Form {
            Text(newEntry.created, style: .date)
                .font(.headline)
            Section("Entry") {
                TextEditor(text: $newEntry.text)
                    .foregroundColor(.secondary)
                    .padding(.horizontal)
                    .frame(minHeight: 80)
                    .onChange(of: newEntry.text) { _ in detector.send() }
                    .onReceive(publisher) {  updateProgressBar() 
                    }
                }
            ProgressView("Character count", value: $progressValue, total: 1.0)
        }
    }
    .toolbar {
        ToolbarItem {
            Button("Add") {
                data.entries.append(newEntry)
                dismiss()
            }
        }
      }
  }
}

struct AddNewEntryView_Previews: PreviewProvider {
    static var previews: some View {
        AddNewEntryView(entryType: .MORNING_PAGES)
    }
}

I almost got it now, but my ProgressView tells me that it cannot convert value of type Double to expected argument type Binding but I’m already using the $dollar sign there. It also says in the second error that initializer(in it value total) requires that binding Conforms to ‘BinaryFloatingPoint’. I’m new to Swift and getting a little lost in the typings here.

Peter Poliwoda
  • 563
  • 2
  • 7
  • 19

2 Answers2

2

You don't need binding, so fix is probably simple

ProgressView("Character count", value: progressValue, total: 1.0)

but you have complicated solution, I recommend to consider much simpler one using only onChange ('cause this calculation is fast and progress can be applied directly, w/o publisher). As well to reset progressValue directly after new entry added.

Here is simplified demo. Tested with Xcode 13.4 / iOS 15.5

struct DemoView: View {
    @State private var text = ""
    @State private var progressValue: Double = 0
    var body: some View {
        VStack {
            Form {
                Section("Entry") {
                    TextEditor(text: $text)
                        .foregroundColor(.secondary)
                        .padding(.horizontal)
                        .frame(minHeight: 80)
                        .onChange(of: text) { value in
                            if value.count <= 100 {
                                progressValue = Double(value.count) / 100.0
                            }
                        }
                }
                ProgressView("Character count", value: progressValue)
            }
        }
    }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Ok, this partially works but the problem is the progress bar only updates when the floating point turns to 1. My length calculation of var charactersCount: Double { Double(newEntry.text.count / MIN_ENTRY_CHARS) } just returns a 0 instead of a floating point 0.1, 0.3… etc – Peter Poliwoda Jul 17 '22 at 19:03
  • OK, I found out what's the problem here. Swift is being very picky when it comes to variable types and because I was dividing two INTs in its head, the result was therefore an int value dropping the decimal point. Changing the count and the limit to a double before division solved the issue. Dang, I spend too much time with JavaScript... – Peter Poliwoda Jul 17 '22 at 19:57
0
    import SwiftUI
    import Combine
    
    struct AddNewEntryView: View {
        @State private var myString: String = ""
        
        var charactersCount: Double {
            Double(myString.count)
        }
        
        var body: some View {
            VStack {
                Form {
                    Section("Entry") {
                        TextEditor(text: $myString)
                            .foregroundColor(.secondary)
                        
                        VStack {
                            ProgressView("minimum character Required", value: charactersCount, total: 100)
                        }
                    }
                    
                    Section("summit") {
                        Button("Post") {
                            
                        }
                        .frame(maxWidth: .infinity)
                        .disabled(charactersCount < 100)
                    }
                }
                
            }
        }
    }
    
    struct AddNewEntryView_Previews: PreviewProvider {
        static var previews: some View {
            AddNewEntryView()
        }
    }