SwiftUI Can't Extract Subview, Core Data, Custom Modifier - xcode

I have a SwiftUI list/detail app with many data fields that are all the same except of
course for the data. I am trying to extract a view but cannot seem to get the
connections correct.
The data is in Core Data and the entity of note is Patient. I pass a patient record to
the detail view. Everything works when the code is repeated for each data field.
While I don't think the issue is with the custom modifier, for clarity I have included
the code for the custom modifier that adds a clear button. Strangely, I could not
find a built-in SwiftUI TextField clear button.
Here is the relevant code for an individual data element - this works:
var patient: Patient
#State private var updatedTitle: String = "No Title"
//title
VStack(alignment: .leading) {
Text("Person Title:")
.padding(.leading, 5)
.font(.headline)
HStack {
TextField("Enter a Title", text: $updatedTitle)
.frame(minWidth: 0, maxWidth: .infinity)
.padding(.trailing, -25)
.padding(.leading, 5)
.frame(height: 45)
.onAppear {
self.updatedTitle = self.patient.title ?? ""
}
Text("")
.frame(width: 35, height: 35)
.modifier(ClearButton(text: self.$updatedTitle))
.padding(.trailing, 15)
.padding(.leading, -20)
}
.background(Color("TextFieldBackground")).cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.gray, lineWidth: 1)
)
.padding(.leading, 10)
.padding(.trailing, 10)
}.padding(10)
This is my attempt to extract a subview:
struct MyDataCell {
#Binding var tfData: String
var passedInLabel: String
var patient: Patient
var body: some View {
VStack(alignment: .leading) {
Text(passedInLabel)
.padding(.leading, 5)
.font(.headline)
HStack {
TextField("Enter a Title", text: $tfData)
.frame(minWidth: 0, maxWidth: .infinity)
.padding(.trailing, -25)
.padding(.leading, 5)
.frame(height: 45)
.onAppear {
self.tfData = self.patient.title ?? ""
}
Text("")
.frame(width: 35, height: 35)
.modifier(ClearButton(text: $tfData))
.padding(.trailing, 15)
.padding(.leading, -20)
}
.background(Color("TextFieldBackground")).cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.gray, lineWidth: 1)
)
.padding(.leading, 10)
.padding(.trailing, 10)
}.padding(10)
}
}
This is the line of code I use to call the subview - the compiler instantly complains
with an error that has nothing to do with this line.
MyDataCell(tfData: $updatedTitle, passedInLabel: "Patient Title", patient: patient)
As mentioned, this is the code for the custom modifier:
struct ClearButton: ViewModifier {
#Binding var text: String
public func body(content: Content) -> some View {
HStack {
content
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.secondary)
}
}
}
}
Xcode Version 11.2 beta 2 (11B44) I have tried both the simulator and a device. Any
guidance would be appreciated.

MyDataCell needs to confirm to View - struct MyDataCell: View {
A property needs to be passed in that is the actual attribute for the Entity. In this case pass in the string of patient.title instead of passing the Entity, Patient. Then in .onAppear use that string.
Lastly, remember the max 10 item per view - break the list into groups.

Related

view display on the right of the window

hello i have an issue with my project on Xcode when I use navigation view, the window display not normally and appear on the right of the window already displayed,
here is the code.
NavigationLink(destination: Acceuil())
{
HStack{
Image("icone_connexion")
.font(.system(size: 15))
// .scaledToFill()
Text("se connecter")
.font(.system(size: 30))
}.cornerRadius(60)
.frame(width: 400, height: 60)
} .background(Capsule().fill(Color(red: 55/255, green: 66/255, blue: 114/255, opacity:1)))
.frame(width: 400, height: 60) //fin navigationlink
.buttonStyle(PlainButtonStyle())
I would like that the new window replace the older one:)
On macOS it is the standard behavior in NavigationView to show the views (parent view and destination view) next to each other: https://developer.apple.com/design/human-interface-guidelines/macos/windows-and-views/column-views/
If you don't want that you can do:
NavigationView {
...
}
.navigationViewStyle(.stack)
so I found this code
import SwiftUI
struct ContentView: View {
#State private var show = false
var body: some View {
VStack{
if !show {
RootView(show: $show)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
.transition(AnyTransition.move(edge: .leading)).animation(.default)
}
if show {
NextView(show: $show)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
.transition(AnyTransition.move(edge: .trailing)).animation(.default)
}
}
}
}
struct RootView: View {
#Binding var show: Bool
var body: some View {
VStack{
Button("Next") { self.show = true }
Text("This is the first view")
}
}
}
it work grate for me so thanks you for your help

Compiler Error: Cannot find variable in scope Xcode Bug

I keep getting a compiler error that's not allowing my app to build.
Below is where I declared the variable showLogOutChoice.
struct HomeView: View {
#EnvironmentObject var userInfo: UserInfo
var showLogOutChoice = false
I then tried to use the value as a trigger for the actionsheet, and it's the compiler is throwing the error that it's not in scope.
.toolbar {
ToolbarItem(placement:.navigationBarLeading){
Button {
} label: {
VStack{
Image("CombIcon")
.resizable()
.frame(width: 40, height: 40)
Text("Menu")
}
}
}
ToolbarItem(placement:.navigationBarTrailing){
Button {
showLogOutChoice = true
} label: {
Text("Log Out ")
}
.confirmationDialog(Text:"Are You Sure You Want To Log Out?", isPresented: $showLogOutChoice , titleVisibility: .visible) {
FBAuth.logout(completion: <#T##(Result<Bool, Error>) -> Void#>)
}
}
}
}
}
The variable should still be in scope, and it's not having the same issue when I change the variable to true. So I'm confused. I tried cleaning the build folder and deleting the derived data but still nothing has changed any suggestions.
I'll copy the full file and leave it below as well for reference. PLEASE HELP!
struct HomeView: View {
#EnvironmentObject var userInfo: UserInfo
var showLogOutChoice = false
//TO DO connect picture to display if picture is not found display person fill
//TO DO add scroll view
var body: some View {
let screenSize: CGRect = UIScreen.main.bounds
let screenHeight = screenSize.height
let screenWidth = screenSize.width
NavigationView{
VStack{
VStack{
if (userInfo.user.profilePictureUrl == "")
{
Image( systemName: "person.fill")
.font(.system(size: 120))
.padding()
.foregroundColor(.black)
}
else{
WebImage(url: URL(string: userInfo.user.profilePictureUrl))
.resizable()
.scaledToFill()
.frame(width: 120, height: 120)
.clipped()
.cornerRadius(120)
}
}
.overlay(RoundedRectangle(cornerRadius: 120)
.stroke(Color.black, lineWidth: 1))
VStack{
Text("Hello \(userInfo.user.firstName)")
.font(.custom("coolvetica", size: 55))
}
VStack(alignment: .leading){
VStack(alignment:.leading){
Text("Your Upcoming Appointment")
.font(.custom("coolvetica", size: 30))
}
ZStack{
RoundedRectangle(cornerRadius: 13, style: .continuous)
.frame(width: screenWidth * 0.9, height: screenHeight * 0.1)
.foregroundColor(.white)
.shadow(color: Color.black.opacity(0.3), radius: 3, x: 0, y: 6)
Text("Holy Grail Appointment - Nov. 19th, 2021 8:00 AM")
.font(.custom("coolvetica", size: 18))
}.padding(.bottom)
HStack{
Button {
//TO DO book an appointment action
} label: {
Text("Book An Appointment")
.font(.custom("coolvetica", size: 20))
.frame(maxWidth: screenWidth * 0.8, minHeight: 20 )
.padding()
.foregroundColor(.white)
.background(Color("steelteal"))
.cornerRadius(42)
}
// Button {
// //TO DO reschedule an appointment action
// // may just make it able to only book an appointment for now
// } label: {
// Text("Reschedule Appointment")
// .font(.custom("coolvetica", size: 15))
// .padding()
// .foregroundColor(.white)
// .background(Color("steelteal"))
// .cornerRadius(42)
//
// }
}
VStack{
Text("My Hair Journey W/ Shei")
.font(.custom("coolvetica", size: 30))
}
HStack{
// need to create logic to save pictures and a note to a specific user
// create checkin page where pictures can be added the date and a note
// create display screen that will pop out with full information once photo is click
// if no checkins/updates are found then display text, no updates found would you like to add one with button
// allow shei to also add updates to individual users
Image( systemName: "person.fill")
.font(.system(size: 80))
.padding()
.foregroundColor(.black)
.overlay(RoundedRectangle(cornerRadius: 2)
.stroke(Color.black, lineWidth: 1))
Image( systemName: "person.fill")
.font(.system(size: 80))
.padding()
.foregroundColor(.black)
.overlay(RoundedRectangle(cornerRadius: 2)
.stroke(Color.black, lineWidth: 1))
Image( systemName: "person.fill")
.font(.system(size: 80))
.padding()
.foregroundColor(.black)
.overlay(RoundedRectangle(cornerRadius: 2)
.stroke(Color.black, lineWidth: 1))
}
}
}
.toolbar {
ToolbarItem(placement:.navigationBarLeading){
Button {
} label: {
VStack{
Image("CombIcon")
.resizable()
.frame(width: 40, height: 40)
Text("Menu")
}
}
}
ToolbarItem(placement:.navigationBarTrailing){
Button {
showLogOutChoice = true
} label: {
Text("Log Out ")
}
.confirmationDialog(Text:"Are You Sure You Want To Log Out?", isPresented: $showLogOutChoice , titleVisibility: .visible) {
FBAuth.logout(completion: <#T##(Result<Bool, Error>) -> Void#>)
}
}
}
}
}
}

"contentMode: .fill" is not working on iOS 15 | SwiftUI Xcode 13

I recently updated to Xcode 13 and I'm facing an issue with one image in my App
This is the same view with the same code in the same device but different iOS versions:
The image I'm using is this (Is a screenshoot instead of original image because original is over 2MB max limit image size of Stack Overflow):
The code:
struct HomeView: View {
#State private var navigateTo: Int? = Links.NIL
// Navigation in root and accent color change.
#Binding var rootViewLinks: RootView.Links
#State var navigationAccentColor: Color = Color(R.color.d_primary()!)
var body: some View {
NavigationView{
VStack{
//Logo and title
HStack{
Image(R.image.ic_logo_miloto_white.name)
.resizable()
.frame(width: 48, height: 56, alignment: .center)
.aspectRatio(contentMode: .fill)
Text("miloto")
.fontWeight(.bold)
.foregroundColor(.white)
.font(.system(size: 48))
.labelStyle(CustomLabelStyle())
}
Spacer()
//Welcome text
Text(R.string.localizable.homeSlogan())
.fontWeight(.bold)
.labelStyle(CustomLabelStyle())
.foregroundColor(.white)
.padding()
//Action buttons
HStack{
//ACCESS NAVIGATION
NavigationLink(
destination: AccessView(rootViewLinks: self.$rootViewLinks, navigationAccentColor: self.$navigationAccentColor),
tag: Links.ACCESS,
selection: $navigateTo){
Button(R.string.localizable.login_text()){
navigateTo = Links.ACCESS
}
.buttonStyle(PrimaryInvertedButtonStyle_W())
}
//REGISTER NAVIGATION
NavigationLink(
destination: RegisterView(rootViewLinks: self.$rootViewLinks, navigationAccentColor: self.$navigationAccentColor),
tag: Links.REGISTER,
selection: $navigateTo){
Button(R.string.localizable.register_text()){
navigateTo = Links.REGISTER
}
.buttonStyle(PrimaryButtonStyle_W())
}
}.padding(.horizontal)
//Tour text
NavigationLink(
destination: TourView(pages: TourPage.getHomeTutorialPages()),
tag: Links.TOUR,
selection: $navigateTo
) {
HStack {
Text(R.string.localizable.tour_text())
.fontWeight(.bold)
.labelStyle(CustomLabelStyle())
.foregroundColor(.white)
Image(R.image.ic_plane.name)
}
.padding(.vertical)
}
}
.background(
Image(R.image.bg_home_big.name)
.resizable()
.aspectRatio(nil, contentMode: .fill)
.ignoresSafeArea()
)
}
.accentColor(navigationAccentColor)
}
But you should focus on this:
.background(
Image(R.image.bg_home_big.name)
.resizable()
.aspectRatio(nil, contentMode: .fill)
.ignoresSafeArea()
)
**Why is image fill not working on iOS 15? Is this a bug? **

How to present views one at a time in SwiftUI

I am looking to make a quiz app using SwiftUI. I have now designed all of the views, but I am having trouble displaying the questions correctly.
I have a home view which for now just has a button that should display a questionView (has only one question) in order and one at a time.
I am using a ForEach to step through each question in a questions array and display a questionView with that specific question, but the problem is that the ForEach displays every question at the same time instead of waiting for the question to be submitted.
How can communicate with the ForEach (or any other function) to tell it when to start and stop showing questionViews.
struct Home: View {
#State var presentQuestionView: Bool = false
#StateObject var questionViewModel: QuestionViewModel = QuestionViewModel()
#State var isCompleted = false
var body: some View {
VStack {
Spacer()
Text("Guess Players")
.font(.title2)
.bold()
.onTapGesture {
presentQuestionView.toggle()
}
Spacer()
}
.sheet(isPresented: $presentQuestionView, content: {
ForEach(questionViewModel.questions.indices) { index in
QuestionView(question: questionViewModel.questions[index], presentQuestionView: $presentQuestionView, progressWidth: questionViewModel.progress(currentIndex: index) )
.offset(x: questionViewModel.currentQuestion().isCompleted ? 1000 : 0)
.rotationEffect(.init(degrees: questionViewModel.currentQuestion().isCompleted ? 1000 : 0))
}
})
}
}
when the button is clicked, I want the ForEach statement to present the QuestionView one at a time.
Here is my QuestionView
struct QuestionView: View {
#ObservedObject var question: Question
#Binding var presentQuestionView: Bool
var progressWidth: CGFloat
#State var isSubmitted = false
#State var buttonText = "Submit"
var body: some View {
VStack {
ZStack(alignment: Alignment(horizontal: .leading, vertical: .center), content: {
Capsule()
.fill(Color.gray.opacity(0.7))
.frame(height: 6)
Capsule()
.fill(Color.green)
.frame(width: progressWidth, height: 6)
})
Text("Guess The Player!")
.font(.system(size: 38))
.fontWeight(.heavy)
.foregroundColor(.purple)
.padding(.top)
Text("Career Averages: ")
.font(.title2)
.fontWeight(.heavy)
.foregroundColor(.black)
.padding(.top, 8)
Text("Points Per Game: \(question.answer.stats!.pointsPerGame)")
.font(.system(size: 20))
.fontWeight(.heavy)
.foregroundColor(.black)
.padding(.top, 5)
Text("Assists Per Game: \(question.answer.stats!.assistsPerGame)")
.font(.system(size: 20))
.fontWeight(.heavy)
.foregroundColor(.black)
.padding(.top, 5)
Text("Rebounds Per Game: \(question.answer.stats!.reboundsPerGame)")
.font(.system(size: 20))
.fontWeight(.heavy)
.foregroundColor(.black)
.padding(.top, 5)
Spacer(minLength: 0)
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 20), count: 2), spacing: 25, content: {
Player_Cards(question: question, isSubmitted: $isSubmitted)
})
.padding()
Spacer(minLength: 0)
Button(action: {
if isSubmitted == false {
//update the score
if question.isCorrect() {
} else {
}
//update the view to show the correct answer
isSubmitted.toggle()
buttonText = "Next Question"
} else {
//Go into the next question
}
}, label: {
Text(buttonText)
.fontWeight(.heavy)
.foregroundColor(.white)
.padding(.vertical)
.frame(maxWidth: .infinity)
.background(Color.blue)
})
}
.background(Color.black.opacity(0.05).ignoresSafeArea())
}
}
This is what currently happens when I run this project

Span two Buttons across HStack in SwiftUI (Big Sur)

I have a problem getting the correct alignment for two buttons (Cancel and OK) in a sheet using SwiftUI.
I want the two buttons to appear at the bottom of the sheet so that the two of them span the whole horizontal width of the sheet (minus padding).
I've found several answers (such as setting the maxWidth to .infinity or by putting the text contend of a button in a separate Text view and surrounding it by Spacers) but none of them seem to work for me. The only one that works somewhat is by creating my own ButtonStyle. But then I have to recreate the whole default ButtonStyle (different for default button, for day and night mode,...)
The code I have now is:
var body: some View {
VStack() {
...
HStack {
Button("Cancel",action: {
isPresented.toggle()
})
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.keyboardShortcut(.cancelAction)
.border(Color.red)
Button("OK", action: {
isPresented.toggle()
let newServer = Server(name: name, url: url, port: port, autoConnect: autoConnect)
do {
try model.add(server: newServer)
} catch {
print("Could not add server.")
}
})
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.keyboardShortcut(.defaultAction)
.border(Color.red)
}
.frame(maxWidth: .infinity)
.padding(.top, 20)
.border(Color.blue)
}
.padding()
.frame(minWidth: 300, maxWidth: 300)
}
This results in the following sheet:
I would like both buttons to fill the area surrounded by the red border.
I'm kinda at a loss on what to try next!
as the red borders show, the buttons do extend to fill the area.
To get the background gray as in the picture, you could use something like:
.background(Color(UIColor.systemGray4))
as the last modifier of each buttons.
Default button style is rendered around provided label content, so possible solution is to calculate needed width for button label dynamically and apply it explicitly.
Note: please also see my comment for alternate
Demo prepared with Xcode 12.5 / macOS 11.3
struct ContentView: View {
#State private var cancelWidth = CGFloat.zero
#State private var okWidth = CGFloat.zero
var body: some View {
VStack() {
Text("Demo here!")
HStack(spacing: 0) {
Button(action: {
}) { Text("Cancel").frame(width: cancelWidth) }
.frame(minWidth: 0, maxWidth: .infinity)
.background(GeometryReader {
Color.clear.preference(key: ViewWidthKey.self,
value: $0.frame(in: .local).size.width) })
.onPreferenceChange(ViewWidthKey.self) { self.cancelWidth = $0 }
.padding()
.border(Color.red)
.keyboardShortcut(.cancelAction)
Button(action: {
}) { Text("OK").frame(width: okWidth) }
.frame(minWidth: 0, maxWidth: .infinity)
.background(GeometryReader {
Color.clear.preference(key: ViewWidthKey.self,
value: $0.frame(in: .local).size.width) })
.onPreferenceChange(ViewWidthKey.self) { self.okWidth = $0 }
.padding()
.keyboardShortcut(.defaultAction)
.border(Color.red)
}
.frame(maxWidth: .infinity)
.padding(.top, 20)
.border(Color.blue)
}
.padding()
.frame(minWidth: 300, maxWidth: 300)
}
}
struct ViewWidthKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}

Resources