SwiftUI: Fonts not shown in Picker? - swiftui

I noticed a strange effect when using fonts in a Picker: While a Text used outside a Picker shows a font correctly, this is not the case if used inside:
#Binding var text: AttributedString
#State private var bold: Bool = false
#State private var fontName: String = ""
#State private var fontSize: CGFloat = 18
#State private var id: Int = 0
init(text: Binding<AttributedString>) {
self._text = text
}
var body: some View {
VStack {
HStack {
Text(text)
.font(Font.custom(fontName, size: 18))
Divider()
Text("\(id)")
}
Picker(
selection: $fontName,
label: Label("Font", systemImage: "")
) {
ForEach(UIFont.familyNames.sorted(), id: \.self) { family in
let names = UIFont.fontNames(forFamilyName: family)
ForEach(names, id: \.self) { name in
Text(name)
.font(Font.custom(name, size: 20))
}
}
}
.onChange(of: fontName, perform: { _ in
update()
})
}
.id(id)
}
private func update() {
var container = AttributeContainer()
.font(.custom(self.fontName, size: self.fontSize))
self.text = self.text.mergingAttributes(
container,
mergePolicy: .keepNew
)
self.id += 1
}
Is this an intended behaviour or a bug? How can I get the picker to show not only the name of the font, but display the text in that font? Actually, after researching, the Text(...).font(...) should work and be sufficient.
Xcode version 14.0 beta 2.

Related

Touch events seemingly not registering at top of screen

I'm seeing very strange behavior within a view. Here's my layout:
struct EventDetailViewContainer: View {
let eventID: EventRecord.ID
#State var event: EventRecord = EventRecord(keyResults: [], text: "", achievesKR: false)
#State var editing: Bool = true
var body: some View {
if #available(iOS 15.0, *) {
VStack {
HStack {
Spacer()
Toggle("Editing", isOn: $editing)
.padding()
}
EventDetailView(event: $event, editing: $editing)
}
} else {
// Fallback on earlier versions
}
}
}
#available(iOS 15.0, *)
struct EventDetailView: View {
#Binding var event: EventRecord
#Binding var editing: Bool
#FocusState var textIsFocused: Bool
var body: some View {
VStack {
TextField(
"Event text",
text: $event.text
)
.focused($textIsFocused)
.disabled(!editing)
.padding()
DatePicker("Event Date:", selection: $event.date)
.disabled(!editing)
.padding()
Toggle("Goal is Reached?", isOn: $event.achievesKR)
.disabled(!editing)
.padding()
HStack {
Text("Notes:")
Spacer()
}
.padding()
TextEditor(text: $event.notes)
.disabled(!editing)
.padding()
Spacer()
}
}
}
struct EventRecord: Identifiable, Equatable {
typealias ID = Identifier
struct Identifier: Identifiable, Equatable, Hashable {
typealias ID = UUID
let id: UUID = UUID()
}
let id: ID
var keyResults: [KeyResult.ID]
var date: Date
var text: String
var notes: String
var achievesKR: Bool
init(
id: ID = ID(),
keyResults: [KeyResult.ID],
date: Date = Date(),
text: String,
notes: String = "",
achievesKR: Bool
) {
self.id = id
self.keyResults = keyResults
self.date = date
self.text = text
self.notes = notes
self.achievesKR = achievesKR
}
}
So this works perfectly when I run it as an iPad app, but when I run it on the simulator, the the top toggle doesn't respond to text input.
The strange thing is, when I simply duplicate the toggle, the top one doesn't work and the bottom one works perfectly:
struct EventDetailViewContainer: View {
let eventID: EventRecord.ID
#State var event: EventRecord = EventRecord(keyResults: [], text: "", achievesKR: false)
#State var editing: Bool = true
var body: some View {
if #available(iOS 15.0, *) {
VStack {
HStack {
Spacer()
Toggle("Editing", isOn: $editing)
.padding()
}
HStack {
Spacer()
Toggle("Editing", isOn: $editing)
.padding()
}
EventDetailView(event: $event, editing: $editing)
}
} else {
// Fallback on earlier versions
}
}
}
It seems like this should be totally unrelated to the touch behavior of the other views.
Btw this is being displayed in the context of a navigation view.
Is there anything that can explain this? And how can I get it working without adding this extra view on top?
edit: Here's a gif of this behavior being demonstrated. The two controls are exactly the same, but the lower one responds to touch and the upper one does not.

Can't transfer variable from one watchos view to another view, Using swiftui

I am trying to get data from one view to another.
I can not figure out how to get values from the fourth view array into the Third view.
I am not using storyboards. I tried using #EnvironmentObject but can not make it work. New to coding. In xcode I am using watchos without app.
I tried to strip out most of the code and leave just the important stuff that can be tested. I used NavigationLink(destination: )to transfer between views.
enter code here
class viewFromEveryWhere: ObservableObject {
#Published var testname2: String = "testTTname"
}
struct secondView: View {
var body: some View {
Text("second view")
List(1..<7) {
Text("\($0)")
}
}
}
struct thirdView: View {
#EnvironmentObject var testname2: viewFromEveryWhere
#EnvironmentObject var testSixTestArray: viewFromEveryWhere
#State var sixTestArray:[String] = ["ww","GS","DW","CD","TS","JW",]
var body: some View {
List(sixTestArray, id:\.self) {
Text($0)
}
}
}
struct fourthView: View {
#StateObject var testname2 = viewFromEveryWhere()
#State private var name: String = ""
#State var testSixTestArray:[String] = []
func collectName () {
print("collectName triggered")
if testSixTestArray.count < 5 {
// testSixTestArray.append(name)
print(name)
print(testSixTestArray)
}
// .enviromentObject(testSixTestArray)
}
var body: some View {
VStack(alignment: . leading) {
Text("Type a name")
TextField("Enter your name", text: $name)
Text("Click to add, \(name)!")
// Button("click this if \(name) is correct") {}
Button(action:{
print("Button Tapped")
collectName()
print(testSixTestArray.count)
name = ""
}) {
Text("Add \(name) to list")
}
// .buttonStyle(GrowingButton1())
}
Text("forth view")
// testSixTestArray.append(name)
.environmentObject(testname2)
}
}
/*func presentTextInputControllerWithSuggestions(forLanguage suggestionsHandler:
((String)-> [Any]?)?,
allowedInputMode inputMode:
WKTextInputMode,
completion: #escaping ([Any]?) -> Void) {}
*/
struct ContentView: View {
#State var sixNameArray:[String] = ["","","","","","",]
#State var messageTextBox: String = "Start"
#State var button1: String = "Button 1"
#State var button2: String = "Button 2"
#State var button3: String = "Button 3"
var body: some View {
NavigationView {
VStack{
Text(messageTextBox)
.frame(width: 120, height: 15, alignment: .center)
.truncationMode(.tail)
.padding()
NavigationLink(destination: secondView(),
label:{
Text(button1)
})
.navigationBarTitle("Main Page")
NavigationLink(destination: thirdView(),
label:{
Text(button2)
})
NavigationLink(destination: fourthView(),
label:{
Text(button3)
})
}
}
}
}
enter code here

How to observe updates in binded values SwiftUI

I'm not sure if I created my custom TextField properly, because I am unable to observe the value changes to an #Binded text. Running the following code, you may observe that print(text) is not executed when you manually enter text into the text field.
import SwiftUI
#main
struct TestOutWeirdTextFieldApp: App {
#State var text: String = "" {
didSet {
print(text)
}
}
var body: some Scene {
WindowGroup {
StandardTextField(placeholderText: "Enter text", defaultText: $text)
}
}
}
struct StandardTextField: View {
#State var placeholderText: String {
didSet {
print(#line)
print(placeholderText)
}
}
#Binding var defaultText: String {
didSet {
print(#line)
print(defaultText)
}
}
#State var systemImage: String?
#State var underlineColor: Color = .accentColor
#State var edges: Edge.Set = .all
#State var length: CGFloat? = nil
#State var secure: Bool = false
var body: some View {
HStack {
if secure {
SecureField(placeholderText, text: $defaultText)
.foregroundColor(underlineColor)
} else {
TextField(placeholderText, text: $defaultText)
.foregroundColor(underlineColor)
}
if let systemImage = systemImage {
Image(systemName: systemImage)
.foregroundColor(.white)
}
}
.overlay(
Rectangle()
.frame(height: 2)
.padding(.top, 35)
)
.foregroundColor(underlineColor)
.padding(edges, length)
}
}
struct StandardTextView_Previews: PreviewProvider {
static var previews: some View {
StandardTextField(placeholderText: "Placement text", defaultText: .constant("")).previewLayout(.sizeThatFits)
}
}
Instead of didSet you need to use .onChange(of: modifier, like
HStack {
// ... your code here
}
.onChange(of: defaultText) { print($0) } // << this !!
.overlay(

SwiftUI different actions on the same Custom Alert actions

I created a custom alert. I want the product to be added to the basket when the Ok button on Alert is clicked on the first screen. When the Ok button is pressed on the second screen, the purchase of the product is requested. I called the same alert on 2 pages and I want it to take different actions. I couldn't do that with #Escaping.
AlertView
struct AlertView: View {
#Binding var openShowAlert: Bool
#State var closeShowAlert: Bool = false
#State var openState: CGFloat = -UIScreen.main.bounds.height
#State var closeState: CGFloat = UIScreen.main.bounds.height
var title: String = ""
var message: String = ""
var okButtonText: String = ""
var cancelButtonText: String = ""
var body: some View {
VStack {
Text(title)
.michromaFont(size: 20)
.padding(.top)
Spacer()
Text(message)
.michromaFont(size: 18)
Spacer()
HStack {
Button(action: {
self.openShowAlert = false
openState = -UIScreen.main.bounds.height
closeState = UIScreen.main.bounds.height
}) {
Text(cancelButtonText)
.foregroundColor(.red)
}
Spacer()
Button(action: {}) {
Text(okButtonText)
}
}
.michromaFont(size: 18)
.padding([.horizontal, .bottom])
}
.neumorphisimBackground(width: 300, height: 200)
.offset(y: self.openShowAlert ? self.openState : self.closeState)
.animation(.easeInOut)
.onChange(of: self.openShowAlert, perform: { value in
if value {
self.openState = .zero
}
})
}
}
DetailView
On this screen, click Alert presentation to add the product to the cart.
struct DetailView: View {
#Environment(\.presentationMode) var presentationMode
var device = UIDevice.current.userInterfaceIdiom
#State var width: CGFloat = 300
#State var height: CGFloat = 450
#Binding var text: String
#State var showAlert: Bool = false
var body: some View {
ZStack() {
......
AlertView(openShowAlert: self.$showAlert)
}
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}
CartView Click I am providing an alert on this screen to purchase the product.
struct CartView: View {
#State var cartList = [1,2,3,4,5,6,7,8,9]
#Environment(\.presentationMode) var presentationMode
#State var showAlert: Bool = false
var body: some View {
ZStack(alignment: .top) {
.....
AlertView(openShowAlert: self.$showAlert)
}
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}
How can I send two different actions in the same alert.
Hmm, I don't see why it shouldn't work with a closure. Have you tried passing over a closure like so?
struct AlertView: View {
...
var okButtonAction: () -> ()
var body: some View {
...
Button(action: okButtonAction) {
Text(okButtonText)
}
}
}
Usage
AlertView(openShowAlert: self.$showAlert) {
// Your custom code
}
Alternative Idea
You could work with Combine and create a publisher with a specific key to identify the sender screen. Then you can put your custom code inside .onReceive().

How do I show a SwiftUI overlay programmatically?

I have a form the User completes which includes an image upload, as this takes a little time to upload to the server I want to show a progress view of the upload.
I have created the view and connected it to the upload progress data which all works fine, and I can have it as a permanent overlay, but I would like to overlay it only when the upload button is pressed.
How do I do that?
my progress view
struct ProgressIndicator: View {
#State var imageUploadAmount = 0.0
var progress: Progress
var body: some View {
VStack {
ProgressView("Uploading…", value: imageUploadAmount, total: 1)
}.onReceive(progress.$upload, perform: { _ in
imageUploadAmount = progress.upload
})
}
}
the form view
struct AddAClimb: View {
#EnvironmentObject var screenCoordinator: ScreenCoordinator
#ObservedObject var progress = Progress()
#State private var showingImagePicker = false
#State private var selectedTypeIndex: Int = 0
#State private var name = ""
#State private var grade = ""
#State private var description = ""
#State private var country = ""
#State private var region = ""
#State private var crag = ""
#State private var section = ""
#State private var height = ""
#State private var long = "0.0"
#State private var lat = "0.0"
#State private var stars = 0
#State private var image: Image? //= "default"
#State private var imageUrl = "default"
#State private var inputImage: UIImage?
#State private var icons = [""]
#State var pitchCount = 1
#State private var pitch = ["",""]
let cloudinary = CLDCloudinary(configuration: ImageController().config)
private var climbTypeOptions = ["Trad", "Sport", "Boulder", "Solo", "Aid"]
var body: some View {
Form{
Section(header: Text("Climb Detial")) {
HStack{
TextField("Climb Name", text: $name)
}
Picker("Climb Type", selection: $selectedTypeIndex){
ForEach(0..<climbTypeOptions.count){
Text(self.climbTypeOptions[$0])
}
}.pickerStyle(SegmentedPickerStyle())
HStack{
Text("Grade:")
TextField("enter the grade", text: $grade)
}
HStack{
Text("Height:")
TextField("enter the height", text: $height)
}
Text("Description:")
TextEditor(text: $description)
HStack{
Text("Pitches:")
Spacer()
Stepper(value: $pitchCount,
in: 1...100,
label: {
Text(" \(self.pitchCount)")
})
}
ForEach(0..<pitchCount){ x in
HStack{
Text("\(x + 1):")
TextField("Pitch Description", text: $pitch[x] )
}
}
}
Section(header: Text("Location")) {
TextField("Country", text: $country )
TextField("Region", text: $region )
TextField("Crag", text: $crag)
TextField("Sector", text: $section)
HStack{
Text("Longitude:")
TextField("Longitude", text: $long).keyboardType(.numbersAndPunctuation)
}
HStack{
Text("Latitude:")
TextField("Latitude", text: $lat).keyboardType(.numbersAndPunctuation)
}
}
Section(header: Text("Images/Videos")) {
image?.resizable().aspectRatio(contentMode: .fit)
HStack{
Spacer()
Button(action: {
self.showingImagePicker = true
}, label: {
Text("Select an Image")
})
Spacer()
}
}
Section(header: Text("Save")) {
HStack{
Spacer()
Button(action:{
ClimbController().addNewClimbToDB(name: name, desc: description, country: country, region: region, crag: crag, sector: section, long: Double(long) ?? 0.0, lat: Double(lat) ?? 0.0, pitches: pitch, stars: stars, grade: grade, climbType: climbTypeOptions[selectedTypeIndex], icons: icons, imageUrl: imageUrl, height: Double(height) ?? 1, inputImage: inputImage, progress: progress, screenCoordinator: screenCoordinator)
print("x")
}){
Text("Add Climb")
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 5, maxHeight: 10)
.font(.title)
.padding()
.background(Color.yellow)
.foregroundColor(.white)
.padding(10)
.border(Color.yellow, width: 5).cornerRadius(8)
}
Spacer()
}
}
}.navigationBarTitle("Add a Climb", displayMode:.inline).sheet(isPresented: $showingImagePicker, onDismiss: loadImage) {
ImagePicker(image: self.$inputImage)
}
}
func loadImage() {
guard let inputImage = inputImage else { return }
image = Image(uiImage: inputImage)
}
}
It is not all components present, but it could be like the following
}.navigationBarTitle("Add a Climb", displayMode:.inline).sheet(isPresented: $showingImagePicker, onDismiss: loadImage) {
ImagePicker(image: self.$inputImage)
}
.overlay(Group {
if self.progress.isUploading {
ProgressIndicator(progress: self.progress) // << here !!
}
})