UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Day 19 Challenge - Interface issues (and posting images in the forum!)

Forums > 100 Days of SwiftUI

I managed to get a working version of the measurements task up and running. The issue that I have been thinking about is what to do with the keyboard on smaller device sizes. This is not just an issue for this app, but anything that uses one of the system keyboards that lacks an obvious means to dismiss it after text entry is complete.

For example, with my app running on an iPod Touch (7th gen) simulator, the keyboard covers the bottom part of the screen, including the result field, and stays there after text entry is complete. I found Paul's post here that discusses this issue, and that at least gave me a way of getting rid of the keyboard.

I ended up with this code:

struct ContentView: View {

    @State private var inputNumber = ""
    @State private var inputUnits = 0
    @State private var outputUnits = 0

    let units = ["m", "km", "feet", "yards", "miles"]

    var convertedMeasurement: Double {
        guard let inputNumber = Double(inputNumber) else {
            return 0
        }
        var conversionToMetresFactor = Double(1)
        var conversionToOutputFactor = Double(1)
        switch inputUnits {
        case 0:
            conversionToMetresFactor = 1.0
        case 1:
            conversionToMetresFactor = 1000.0
        case 2:
            conversionToMetresFactor = 0.3048
        case 3:
            conversionToMetresFactor = 0.9144
        case 4:
            conversionToMetresFactor = 1609.344
        default:
            conversionToMetresFactor = 1.0
        }
        switch outputUnits {
        case 0:
            conversionToOutputFactor = 1.0
        case 1:
            conversionToOutputFactor = 0.001
        case 2:
            conversionToOutputFactor = 3.2804
        case 3:
            conversionToOutputFactor = 1.093613
        case 4:
            conversionToOutputFactor = 0.0006213712
        default:
            conversionToOutputFactor = 1.0
        }
        return(inputNumber * conversionToMetresFactor * conversionToOutputFactor)
    }

    var body: some View {
        NavigationView {
        Form {
            Section(header: Text("Enter your measurement")) {
                HStack {
                TextField("Number", text: $inputNumber)
                    .keyboardType(.decimalPad)
                    Button {
                        hideKeyboard()
                    } label: {
                        Image(systemName: "keyboard.chevron.compact.down")
                    }
                }
                Picker("Units", selection: $inputUnits) {
                    ForEach(0 ..< units.count) {
                        Text(units[$0])
                    }
                }
                .pickerStyle(SegmentedPickerStyle())
            }
            Section(header: Text("Converted value:")) {
                Text("\(self.convertedMeasurement, specifier:"%.2f")")
                Picker("Units", selection: $outputUnits) {
                    ForEach(0 ..< units.count) {
                        Text(units[$0])
                    }
                }
                .pickerStyle(SegmentedPickerStyle())
            }
        }
        .navigationTitle(Text("Convertor"))
    }

    }

}

That works; the hideKeyboard() function is copied from the post linked above. However, it means that there is a button showing the 'hide keyboard' icon visible all the time, even when the keyboard is not showing. So I suppose I could try to hide the button at the same time as the keyboard, and show it again when the user starts to enter text in the input number field. Perhaps I need a Bool to track whether the button should be shown... but then it's not needed at all for most devices, so I would need to check for screen size, which sounds like a very bad idea (surely I should let SwiftUI take care of details like that...)

Any ideas about how to deal with this kind of interface issue on smaller screens?

Also, can anyone tell me how to include an image in a post? I've tried the 'Insert Image' button, but I can't work out what to do after that...

2      

You can do this:

NavigationView {
    //... your existing code, minus the hide keyboard button, which is no longer necesary
}
.onTapGesture {
    hideKeyboard()
}

With this, any tap on the screen outside the keyboard area will trigger hideKeyboard().

Also, can anyone tell me how to include an image in a post? I've tried the 'Insert Image' button, but I can't work out what to do after that...

You have to have the image stored somewhere. Like on imgur or dropbox or... I dunno what else people are using these days. Then you put a title for the image in the square brackets and the URL to wherever the image is stored in the parentheses.

![image title](image URL)

3      

Many thanks - that does get rid of the keyboard.

However, this approach creates another problem - the user cannot now tap any of the buttons in the UI, because the NavigationView is absorbing all of the tap gestures. Is there a way to pass through the gesture - so that the tap continues though to the buttons after calling hideKeyboard() ?

2      

Yeah, okay, that is a problem.

So, going back to your original solution with a button to hide the keyboard...

  1. Add a property to track whether the button is displayed or not:
@State private var showKeyboardButton = false
  1. Wrap the button in a conditional:
if showKeyboardButton {
    Button {
        hideKeyboard()
        showKeyboardButton = false
    } label: {
        Image(systemName: "keyboard.chevron.compact.down")
    }
}
  1. Add an onReceive handler to watch for keyboardWillShowNotification and change whether the button is displayed:
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { _ in
    showKeyboardButton = true
}

How's that work for you?

2      

It's better, but still not quite right!

With the code below, I can now use the button to get rid of the keyboard (I enabled/disabled the button rather than removing it, which made the UI jump around alarmingly!) The problem now is that the tap on the keyboard button also triggers the 'number of people' picker - the target area for which seems to be much bigger than I expected! I think there may be something wrong with the way my views are set up... I will have another look at this.

import SwiftUI

#if canImport(UIKit)
extension View {
    func hideKeyboard() {
        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}
#endif

struct ContentView: View {
    @State private var checkAmount = ""
    @State private var numberOfPeople = 2
    @State private var tipPercentage = 2
    @State private var keyboardButtonDisabled = true

    let tipPercentages = [10, 15, 20, 25, 0]

    var totalPerPerson: Double {
        let actualPeople = Double(numberOfPeople + 2)
        let actualPercentage = Double(tipPercentages[tipPercentage])
        guard let actualAmount = Double(checkAmount) else {
            return 0.0
        }
        return((actualAmount * (100 + actualPercentage) / 100)/actualPeople)

    }
    var body: some View {
        NavigationView {
            Form {
                Section {
                    VStack(spacing:30)
                    {
                        HStack {
                            TextField("Amount", text: $checkAmount)
                                .keyboardType(.decimalPad)
                                .background(tipPercentage == 4 ? Color.red : Color.white)

                            Button {
                                hideKeyboard()
                            } label: {
                                Image(systemName: "keyboard.chevron.compact.down")
                            }
                            .disabled(keyboardButtonDisabled)

                        }
                        // Spacer()
                        Picker("Number of People", selection:
                                $numberOfPeople) {
                            ForEach(2 ..< 100) {
                                Text("\($0) people")
                            }
                        }
                    }
                }
                Section(header: Text("How much tip do you want to leave?")) {

                    Picker("Tip Percentage", selection: $tipPercentage) {
                        ForEach(0 ..< tipPercentages.count) {
                            Text("\(self.tipPercentages[$0])%")
                        }
                    }
                    .pickerStyle(SegmentedPickerStyle())
                }
                .textCase(nil)
                Section {
                    Text("$\(totalPerPerson, specifier: "%.2f")")
                }
            }
            .navigationTitle("WeSplit")
            .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification), perform: { _ in
                keyboardButtonDisabled = false
            })
        }
    }
}

Thanks again for your help with this - it's really useful. I'm not sure that the idea of removing controls at runtime will work - perhaps I should keep the button in place and use the showKeyboardButton flag to enable/disable it as needed?

2      

A little more experimentation, and I came up with this code, which seems to work as I want; it is now possible to dismiss the keyboard using the button, and the pickers work correctly.

struct ContentView: View {
    @State private var checkAmount = ""
    @State private var numberOfPeople = 2
    @State private var tipPercentage = 2
    @State private var keyboardButtonDisabled = true

    let tipPercentages = [10, 15, 20, 25, 0]

    var totalPerPerson: Double {
        let actualPeople = Double(numberOfPeople + 2)
        let actualPercentage = Double(tipPercentages[tipPercentage])
        guard let actualAmount = Double(checkAmount) else {
            return 0.0
        }
        return((actualAmount * (100 + actualPercentage) / 100)/actualPeople)
    }

    func hideKeyboard() {
        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
        keyboardButtonDisabled = true
    }

    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Enter the bill, and the number of people:")) {
                    HStack {
                        TextField("Amount", text: $checkAmount)
                            .keyboardType(.decimalPad)
                            .background(tipPercentage == 4 ? Color.red : Color.white)
                        Button {
                            hideKeyboard()
                        } label: {
                            Image(systemName: "keyboard.chevron.compact.down")
                        }
                        .disabled(keyboardButtonDisabled)
                    }
                    Picker("Number of People", selection:
                            $numberOfPeople) {
                        ForEach(2 ..< 100) {
                            Text("\($0) people")
                        }
                    }
                }
                .textCase(nil)

                Section(header: Text("How much tip do you want to leave?")) {

                    Picker("Tip Percentage", selection: $tipPercentage) {
                        ForEach(0 ..< tipPercentages.count) {
                            Text("\(self.tipPercentages[$0])%")
                        }
                    }
                    .pickerStyle(SegmentedPickerStyle())
                }
                .textCase(nil)

                Section(header: Text("Amount to pay per person:")) {
                    Text("$\(totalPerPerson, specifier: "%.2f")")
                }
                .textCase(nil)
            }
            .navigationTitle("WeSplit")
        }
        .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification), perform: { _ in
            keyboardButtonDisabled = false
        })
    }
}

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.