How to make a button clickable to another view controller only with textfield - swift

I have this scenario that if the user clicks the button then another view controller will popuped which includes only a textView where the user can write something inside.

You can use the the .sheet modifier to display a modal / popup like view in SwiftUI.
struct ContentDetail: View {
struct Item {
let uuid = UUID()
let value: String
}
#State private var items = [Item]()
#State private var show_modal = false
var lectureName:String
var body: some View {
ZStack {
VStack {
Spacer()
HStack {
Spacer()
Button(action: {
self.show_modal.toggle()
}, label: {
Text("✏️")
.font(.system(.largeTitle))
.frame(width: 77, height: 70)
.foregroundColor(Color.white)
.padding(.bottom, 7)
})
.background(Color.blue)
.cornerRadius(38.5)
.padding()
.shadow(color: Color.black.opacity(0.3),
radius: 5,
x: 3,
y: 3)
}
}
}
.sheet(isPresented: self.$show_modal) {
CustomModalView()
}
}
}
struct CustomModalView: View {
#State private var text = ""
var body: some View {
TextField("test", text: $text)
.padding(5)
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.system(size: 60, design: .default))
.multilineTextAlignment(.center)
}
}
You can read more about it here:
https://blog.appsbymw.com/posts/how-to-present-and-dismiss-a-modal-in-swiftui-155c/

Related

How do I fix buttons not being tappable in a ForEach loop?

I have a view which contains a button. The view with the button is created from a forEach loop. For some reason only some buttons are tappable and others are not.
The parent view contains a NavigationView, a scroll view inside the NavigationView, a lazyVStack inside of the scroll view, a forEachloop in that lazyVStack and in that for loop is the child view that contains the button.
struct ContentView: View {
let peoples:[Person] = Bundle.main.decode("data.json")
var body: some View {
let columns = [
GridItem(.flexible(minimum: 300), spacing: 10)
]
NavigationView {
ScrollView(.vertical) {
LazyVStack {
ForEach(peoples, id: \.self) { person in
PersonView(name: person.Name, age: person.Age)
}
}
.navigationTitle("A list of people")
.navigationViewStyle(DefaultNavigationViewStyle())
.padding()
}
}
}
}
The child view is bellow. I suspect the scroll view is stealing the user input, but I am not sure why or how to overcome it. Some buttons are tapeable and some are not.
struct PersonView: View {
#Environment(\.colorScheme) var colorScheme
var name: String
var age: Int
var body: some View {
VStack(alignment:.leading) {
Image("randoPerson")
.resizable()
.scaledToFill()
.frame(minWidth: nil, idealWidth: nil,
maxWidth: UIScreen.main.bounds.width, minHeight: nil,
idealHeight: nil, maxHeight: 300, alignment: .center)
.clipped()
VStack(alignment: .leading, spacing: 6) {
Text("name")
.fontWeight(.heavy)
.padding(.leading)
Text("Age \(age)")
.foregroundColor(Color.gray)
.padding([.leading,.bottom])
Button(action: { print("I was tapped") }) {
HStack {
Image(systemName: "message.fill")
.font(.title)
.foregroundColor(.white)
.padding(.leading)
Text("Message them")
.font(.subheadline)
.foregroundColor(.white)
.padding()
}
.background(Color.blue)
}
.padding()
}
.background(Color(UIColor.systemBackground).cornerRadius(15))
.shadow(color:colorScheme == .dark
? Color.white.opacity(0.2)
: Color.black.opacity(0.2),
radius: 7, x: 0, y: 2)
}
}
}
To fix the issue, you can add an id: UUID associated to a Person and iterate between the Person, inside the ForEach using their ID.
You will also noticed that I added the value as lowercases to respect the Swift convention.
struct Person {
let id: UUID // Add this value
var name: String
var age: Int
}
So here is the ContentView with the id replacing \.self:
struct ContentView: View {
let peoples: [Person] = Bundle.main.decode("data.json")
var body: some View {
NavigationView {
ScrollView(.vertical) {
LazyVStack {
ForEach(peoples, id: \.id) { person in // id replace \.self here
PersonView(name: person.name, age: person.age) // removed uppercase
}
}
.navigationTitle("A list of people")
.navigationViewStyle(DefaultNavigationViewStyle())
.padding()
}
}
}
}

Style the picker in SwiftUI

I'm doing a login form in Swiftui, however when i try to add a form that contain picker for age the style get messed up and since im new to SwiftUI i didn't know how can i fix this problem?
How can i fix the picker style so it addapte with the Nickname field?
here it is the code that i tried:
#State var nickName: String = ""
#State var age: Int = 18
var body: some View {
VStack {
HStack {
Image(systemName: "person.circle.fill")
.foregroundColor(Color("ChatiwVeryLightBlue"))
.frame(width: 44, height: 44)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.shadow(color: Color.black.opacity(0.15), radius: 5, x: 0, y: 5)
.padding(.all, 10)
TextField("Nickname", text: $nickName)
.frame(maxWidth: .infinity)
.frame(height: 50)
}
Divider().padding(.leading, 80)
HStack {
Image(systemName: "person.circle.fill")
.foregroundColor(Color("ChatiwVeryLightBlue"))
.frame(width: 44, height: 44)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.shadow(color: Color.black.opacity(0.15), radius: 5, x: 0, y: 5)
.padding(.all, 10)
Form {
Section {
Picker(selection: $age, label: Text("Picker")) {
ForEach(18 ..< 99, id: \.self) { index in
Text("\(index)").tag(1)
}
}
}
}
}
// Age
// Country
// Male/Female
}
.background(BlurView(blurType: .systemMaterial))
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.padding()
.offset(y: 400)
Not sure if you are still working on this project, but here is a neat workaround:
struct ContentView: View {
#State private var age = 18
var body: some View {
NavigationView {
VStack {
Divider()
HStack {
NavigationLink(destination: AgePickerView(age: $age)) {
HStack {
Text("Age")
Spacer()
Text("\(age) >")
.padding(.trailing)
}
}
}
.foregroundColor(.gray)
Divider()
}
.padding(.leading, 80)
}
}
struct AgePickerView: View {
#Binding var age: Int
var body: some View {
List(18..<99) { index in
Button(action: {
self.age = index
}) {
HStack {
Text("\(index)")
Spacer()
if index == age {
Image(systemName: "checkmark")
.foregroundColor(.blue)
}
}
}
}
}
}
}
To get the same NavigationView style but remove the Form style, you need to wrap a NavigationLink in a NavigationView.
The little > sign I put at the end of the Text view in the NavigationLink doesn't perfectly match the one in the Form style, so you can look around for an image that is more similar if you would like.
The AgePickerView is pretty straightfoward; it's just a list of buttons that are modifying a binding to the original state variable in ContentView through tap gestures. The checkmark is there to imitate a Picker view.
The best part of this is that you don't have to worry about all that index shifting with a picker.

SwiftUI - How to close a fake Modal View - from the second view inside it, with a close button?

I have a tricky design. I'm trying to have a close button that from whatever views, inside a "fake" Modal View (fake cause it's full screen, thanks to a code that i found online), it's gonna close the Modal View.
Right now the close button is working in the first view that open in the modal view, but I need it to work also in the next views of the modal view, cause I have a flow inside this modal view to create an item.
This is the View from which I start ColorNewItemView, my fake Modal View.
struct RecapItemToAdd: View {
#State var isPresented: Bool = false
var body: some View {
NavigationView {
VStack {
Button(action: { withAnimation { self.isPresented.toggle()}}) {
Image(systemName: "pencil")
.resizable()
.renderingMode(.original)
.frame(width: 13, height: 17)
.foregroundColor(UIManager.hLightGrey)
}
}
ZStack {
VStack(alignment: .leading) {
ColorNewItemView(isPresenteded: self.$isPresented)
Spacer()
}
}
.background(Color.white)
.edgesIgnoringSafeArea(.all)
.offset(x: 0, y: self.isPresented ? 0 : UIApplication.shared.keyWindow?.frame.height ?? 0)
}
}
}
Note: I know that "keyWindow" is deprecated but I don't know how to change it.
With ColorNewItemView starts my full screen Modal View. In this view the close button works.
struct ColorNewItemView: View {
#State var selection: Int? = nil
#Binding var isPresenteded: Bool
var body: some View {
NavigationStackView {
VStack(alignment: .center) {
Button(action: {
self.isPresenteded = false
}) {
Image(systemName: "xmark.circle.fill")
.resizable()
.frame(width: 30, height: 30)
.foregroundColor(UIManager.hBlueLight)
}
Text("First View")
.font(UIManager.einaTitle)
.foregroundColor(UIManager.hDarkBlue)
Image("black-hoodie")
.resizable()
.renderingMode(.original)
.frame(width: 245, height: 300)
PushView(destination: Color2NewItemView(isPresenteded: self.$isPresenteded), tag: 1, selection: $selection) {
Button(action: {self.selection = 1}) {
Text("Avanti")
.font(UIManager.einaButton)
.foregroundColor(.white)
.frame(width: 291, height: 43)
.background(UIManager.buttonGradient)
.cornerRadius(6)
.shadow(color: UIManager.hBlueShadow, radius: 7, x: 0.0, y: 6.0)
}
}
}
}
}
}
Now I have the next view inside the Modal view, where the close button starts to stop working.
struct Color2NewItemView: View {
#Binding var isPresenteded: Bool
#State var selection: Int? = nil
var body: some View {
VStack(alignment: .center) {
Button(action: {
self.isPresenteded = false
}) {
Image(systemName: "xmark.circle.fill")
.resizable()
.frame(width: 30, height: 30)
.foregroundColor(UIManager.hBlueLight)
}
Text("Second View")
.font(UIManager.einaTitle)
.foregroundColor(UIManager.hDarkBlue)
Image("black-hoodie")
.resizable()
.renderingMode(.original)
.frame(width: 245, height: 300)
PushView(destination: FabricNewItemView(isPresenteded: $isPresenteded), tag: 1, selection: $selection) {
Button(action: {self.selection = 1}) {
Text("Tessuto")
.font(UIManager.einaButton)
.foregroundColor(.white)
.frame(width: 291, height: 43)
.background(UIManager.buttonGradient)
.cornerRadius(6)
.shadow(color: UIManager.hBlueShadow, radius: 7, x: 0.0, y: 6.0)
}
}
Spacer()
.frame(height: 18)
PopView{
Text("Back")
.font(UIManager.einaBodySemibold)
.foregroundColor(UIManager.hGrey)
}
}
}
}
Ps.
I had also to use a library called NavigationStack, since I have a custom back button on the bottom of the page, and the Navigation View doesn't let me pop back without using the back in the navigation bar.
Binding can be lost on deep view hierarchy, so it is more appropriate to operate with it on the level it was received.
Here is possible approach using EnvironmentKey (by same idea as presentationMode works)
Introduce helper environment key which holds some closure
struct DismissModalKey: EnvironmentKey {
typealias Value = () -> ()
static let defaultValue = { }
}
extension EnvironmentValues {
var dismissModal: DismissModalKey.Value {
get {
return self[DismissModalKey.self]
}
set {
self[DismissModalKey.self] = newValue
}
}
}
so in your top modal view you can inject into hierarchy callback to dismiss
struct ColorNewItemView: View {
#State var selection: Int? = nil
#Binding var isPresented: Bool
var body: some View {
NavigationStackView {
// ... other code
}
.environment(\.dismissModal, { self.isPresented = false} ) // << here !!
}
}
thus this environment value now is available for all subviews, and you can use it as
struct Color2NewItemView: View {
#Environment(\.dismissModal) var dismissModal
#State var selection: Int? = nil
var body: some View {
VStack(alignment: .center) {
Button(action: {
self.dismissModal() // << here !!
}) {
// ... other code
}
}

SwiftUI passing a saved value to parent view

I am fairly new to SwiftUI I am trying to figure out the best way to pass data from a child view to parent?
Thanks for the help I come from a Javascript (React) background so this is a little different for me
The way my child view works is the user clicks on an image to select that image.
I have #State binding that saves the imgUrl which is a String referring to name in Assets.
I am just not sure about the best way to pass that value to the parent component.
Here is the child view (imageSelector)
struct ImageSelector: View {
#State private var windowImgs = ["1", "2", "3","4","5","6","7","8","9","10","11","12","13", "14","15","16","17","18"]
#State private var imgPicked = ""
var body: some View{
ScrollView(Axis.Set.horizontal, showsIndicators: true){
HStack{
ForEach(0..<18){num in
Button(action:{
self.imgPicked = self.windowImgs[num]
print(self.imgPicked)
}){
Image("\(self.windowImgs[num])")
.renderingMode(.original)
.resizable()
.cornerRadius(4)
.frame(width: 100, height: 100)
}
}
}
}
}
}
Here is the parent view (AddCounterForm)
struct AddCounterForm: View {
#Environment(\.presentationMode) var presentationMode
#State private var pickedImg: String = "defaultImg"
#State private var price: String = "0.0"
#State private var qty: String = "0"
var body: some View {
VStack (spacing: 40){
HStack {
Button("Cancel"){
self.presentationMode.wrappedValue.dismiss()
}
.foregroundColor(.red)
Spacer()
Button("Save"){
}
}
HStack {
VStack (spacing: 20){
TextField("Window type", text: /*#START_MENU_TOKEN#*//*#PLACEHOLDER=Value#*/.constant("")/*#END_MENU_TOKEN#*/)
TextField("Window location", text: /*#START_MENU_TOKEN#*//*#PLACEHOLDER=Value#*/.constant("")/*#END_MENU_TOKEN#*/)
}
.textFieldStyle(RoundedBorderTextFieldStyle())
Image(pickedImg)
.resizable()
.cornerRadius(4)
.frame(width: 90, height: 90)
.padding(.leading)
}
HStack {
Text("Price")
TextField("", text:$price)
.frame(width: 70)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
Spacer()
Text("Qty")
TextField("", text:$qty)
.frame(width: 70)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
}
VStack {
Text("Select an image")
.foregroundColor(.blue)
ImageSelector()
.padding(.bottom)
Button("Use your own image"){
//method
}
.frame(width: 180, height: 40)
.background(Color.blue)
.clipShape(Capsule())
.foregroundColor(.white)
.padding(.top)
}
}
.padding()
}
}
Solution for preview thanks for the help from #Asperi & #neverwinterMoon
struct ImageSelector_Previews: PreviewProvider {
static var previews: some View {
PreviewWrapper()
}
}
struct PreviewWrapper: View {
#State(initialValue: "") var imgPicked: String
var body: some View {
ImageSelector(imgPicked: $imgPicked)
}
}
In this case Binding is most appropriate
struct ImageSelector: View {
#Binding var imgPicked: String
and use
ImageSelector(imgPicked: $pickedImg)
.padding(.bottom)

How to add click event to whole view in SwiftUI

I'm developing SwiftUI test app and I added my custom DropDown menu here.
Dropdown works fine but I want to dismiss dropdown menu when user click dropdown menu outside area.
Here's my dropdown menu.
import SwiftUI
var dropdownCornerRadius:CGFloat = 3.0
struct DropdownOption: Hashable {
public static func == (lhs: DropdownOption, rhs: DropdownOption) -> Bool {
return lhs.key == rhs.key
}
var key: String
var val: String
}
struct DropdownOptionElement: View {
var dropdownWidth:CGFloat = 150
var val: String
var key: String
#Binding var selectedKey: String
#Binding var shouldShowDropdown: Bool
#Binding var displayText: String
var body: some View {
Button(action: {
self.shouldShowDropdown = false
self.displayText = self.val
self.selectedKey = self.key
}) {
VStack {
Text(self.val)
Divider()
}
}.frame(width: dropdownWidth, height: 30)
.padding(.top, 15).background(Color.gray)
}
}
struct Dropdown: View {
var dropdownWidth:CGFloat = 150
var options: [DropdownOption]
#Binding var selectedKey: String
#Binding var shouldShowDropdown: Bool
#Binding var displayText: String
var body: some View {
VStack(alignment: .leading, spacing: 0) {
ForEach(self.options, id: \.self) { option in
DropdownOptionElement(dropdownWidth: self.dropdownWidth, val: option.val, key: option.key, selectedKey: self.$selectedKey, shouldShowDropdown: self.$shouldShowDropdown, displayText: self.$displayText)
}
}
.background(Color.white)
.cornerRadius(dropdownCornerRadius)
.overlay(
RoundedRectangle(cornerRadius: dropdownCornerRadius)
.stroke(Color.primary, lineWidth: 1)
)
}
}
struct DropdownButton: View {
var dropdownWidth:CGFloat = 300
#State var shouldShowDropdown = false
#State var displayText: String
#Binding var selectedKey: String
var options: [DropdownOption]
let buttonHeight: CGFloat = 30
var body: some View {
Button(action: {
self.shouldShowDropdown.toggle()
}) {
HStack {
Text(displayText)
Spacer()
Image(systemName: self.shouldShowDropdown ? "chevron.up" : "chevron.down")
}
}
.padding(.horizontal)
.cornerRadius(dropdownCornerRadius)
.frame(width: self.dropdownWidth, height: self.buttonHeight)
.overlay(
RoundedRectangle(cornerRadius: dropdownCornerRadius)
.stroke(Color.primary, lineWidth: 1)
)
.overlay(
VStack {
if self.shouldShowDropdown {
Spacer(minLength: buttonHeight)
Dropdown(dropdownWidth: dropdownWidth, options: self.options, selectedKey: self.$selectedKey, shouldShowDropdown: $shouldShowDropdown, displayText: $displayText)
}
}, alignment: .topLeading
)
.background(
RoundedRectangle(cornerRadius: dropdownCornerRadius).fill(Color.white)
)
}
}
struct DropdownButton_Previews: PreviewProvider {
static let options = [
DropdownOption(key: "week", val: "This week"), DropdownOption(key: "month", val: "This month"), DropdownOption(key: "year", val: "This year")
]
static var previews: some View {
Group {
VStack(alignment: .leading) {
DropdownButton(displayText: "This month", selectedKey: .constant("Test"), options: options)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
.foregroundColor(Color.primary)
VStack(alignment: .leading) {
DropdownButton(shouldShowDropdown: true, displayText: "This month", selectedKey: .constant("Test"), options: options)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
.foregroundColor(Color.primary)
}
}
}
I think I can achieve this by adding click event to whole body view and set dropdown show State flag variable to false.
But I'm not sure how to add click event to whole view.
Can anyone please help me about this issue?
Thanks.
You can try like the following in your window ContentView
struct ContentView: View {
var body: some View {
GeometryReader { gp in // << consumes all safe space
// all content here
}
.onTapGesture {
// change state closing any dropdown here
}
}
// .edgesIgnoringSafeArea(.all) // uncomment if needed entire screen
}
can be done adding .contentShape(Rectangle()) to HStack/VStack before .onTapGesture
for example
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
VStack(alignment:.leading, spacing: 8) {
CustomText(text: model?.id ?? "Today", fontSize: 12, fontColor: Color("Black50"))
CustomText(text: model?.title ?? "This Day..", fontSize: 14)
.lineLimit(2)
.padding(.trailing, 8)
}
Spacer()
Image("user_dummy")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 60)
.cornerRadius(8)
}
CustomText(text: model?.document ?? "", fontSize: 12, fontColor: Color("Black50"))
.lineLimit(4)
}
.contentShape(Rectangle())
.onTapGesture {
debugPrint("Whole view as touch")
}
}