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 !!
}
})
Related
I am creating a feature where a user can take a picture of an item and that image can be displayed on their home screen. I am currently allowing them to take a picture in the AddProductView screen and I want the picture that they took/choose to be shown on the HomeScreenView. I tried to put a #Binding var productImage = UIImage() in the HomeScreenView but that is giving me a
No exact matches in a call to initializer
Here is the HomeScreenView:
struct HomeScreenView: View {
#Binding var loggedin: Bool
#State var showSheet: Bool = false
#Binding var productImage = UIImage()
var body: some View {
NavigationView {
VStack {
Text("Authorized.... You are in!")
.font(.largeTitle)
}
.padding()
.navigationTitle("Home")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing){
Button () {
showSheet.toggle()
} label: {
Text("Add Item")
}
.sheet(isPresented: $showSheet, content: {
AddProductView()
})
}
}
}
.environment(\.colorScheme, .light)
.navigationBarBackButtonHidden(true)
}
}
Here is the AddProductView:
struct AddProductView: View {
#Environment(\.presentationMode) var presentationMode
#State var itemImage = false
#State var openCameraRoll = false
#State var camera = false
#State var photoLib = false
#State private var productimage = UIImage()
#State var productname = ""
#State var productcategory = ""
#State var otherproductcategory = ""
let category = ["Living Room", "Bedroom", "Kitchen", "Garage", "Office", "Electronics", "Clothes"]
var body: some View {
NavigationView {
VStack{
Form{
Section(header: Text("Add An Image")) {
if itemImage {
Image(uiImage: productimage)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(alignment: .center)
} else {
Image("ImageButton")
.frame(width: 300, alignment: .center)
}
HStack{
Button(action: {
itemImage = true
openCameraRoll = true
camera = true
}, label: {
Text("Camera")
.frame(maxWidth: .infinity, alignment: .center)
})
.buttonStyle(BorderlessButtonStyle())
Divider()
Button(action: {
itemImage = true
openCameraRoll = true
camera = false
photoLib = true
}, label: {
Text("Photos")
.frame(maxWidth: .infinity, alignment: .center)
})
.buttonStyle(BorderlessButtonStyle())
}
}
Section(header: Text("Item Information")) {
TextField("Item Name", text: $productname)
Picker("Category", selection: $productcategory) {
ForEach(category, id: \.self){
Text("\($0)")
}
Text("Other").tag("other")
}
if productcategory == "other" {
TextField("Other Provider", text: $otherproductcategory)
}
}
Button(action: {
presentationMode.wrappedValue.dismiss()
}, label: {
Text("Submit")
.frame(maxWidth: .infinity, alignment: .center)
})
}
}
.navigationTitle("Add An Item")
.sheet(isPresented: $openCameraRoll) {
if camera {
ImagePicker(selectedImage: $productimage, sourceType: .camera)
} else {
ImagePicker(selectedImage: $productimage, sourceType: .photoLibrary)
}
}
}
}
Here is where the Image is being set to a variable:
.sheet(isPresented: $openCameraRoll) {
if camera {
ImagePicker(selectedImage: $productimage, sourceType: .camera)
} else {
ImagePicker(selectedImage: $productimage, sourceType: .photoLibrary)
}
}
I tried to use #Binding var productImage = UIImage() but I get a "No exact matches in a call to initializer"
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.
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
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().
When I type some text in one of the text fields, somehow it adds to all the text fields the same text at the same time. I am trying to separate them from each other so I can assign them correctly.
struct CardInfo : View {
#State var creditCard : CreditCard
#State var isSaved: Bool = false
#State private(set) var text = ""
var body: some View {
VStack {
CustomTextField(data: $text, tFtext: "Kartin Uzerindeki Isim", tFImage: "user")
.textContentType(.givenName)
.onReceive(Just(text)) { data in
self.creditCard.cardOwnerName = self.text
}
CustomTextField(data: $text, tFtext: "Kredi Kart Numarasi", tFImage: "credit")
.textContentType(.oneTimeCode)
.keyboardType(.numberPad)
.onReceive(Just(text)) { data in
self.creditCard.cardNumber = self.text
}
struct CustomTextField: View {
#Binding var data : String
var tFtext: String = ""
var tFImage: String = ""
var body: some View {
HStack {
Image(tFImage)
.resizable()
.frame(width: 20, height: 20)
.padding()
TextField(tFtext, text: $data)
.padding()
.font(Font.custom("SFCompactDisplay", size: 16))
.foregroundColor(.black)
}
.background(RoundedRectangle(cornerRadius: 10))
.foregroundColor(Color(#colorLiteral(red: 0.9647058824, green: 0.9725490196, blue: 0.9882352941, alpha: 1)))
}
}
You need to use separate #State variables for each TextField:
struct CardInfo : View {
#State var creditCard : CreditCard
#State var isSaved: Bool = false
#State private(set) var cardOwnerName = ""
#State private(set) var cardNumber = ""
var body: some View {
VStack {
CustomTextField(data: $cardOwnerName, tFtext: "Kartin Uzerindeki Isim", tFImage: "user")
.textContentType(.givenName)
.onReceive(Just(cardOwnerName)) { data in
self.creditCard.cardOwnerName = data
}
CustomTextField(data: $cardNumber, tFtext: "Kredi Kart Numarasi", tFImage: "credit")
.textContentType(.oneTimeCode)
.keyboardType(.numberPad)
.onReceive(Just(cardNumber)) { data in
self.creditCard.cardNumber = data
}
...
}
}
}