onTapGesture fails whole appliaction - swift

I am making a little game with some youtube tutorials, I made the design but whenever I try to add onTapGesture my xCode returns an error Cannot Preview This File, and the simulator launches but it's just all white
struct ContentView: View {
var body: some View {
HStack{
CardView()
CardView()
CardView()
CardView()
}
.foregroundColor(.orange)
.padding(.horizontal)
}
}
struct CardView:View {
#State var isFaceUp:Bool = false
var body: some View {
ZStack{
let shape = RoundedRectangle(cornerRadius: 20)
if isFaceUp {
shape.stroke(lineWidth: 3)
Text("🧞‍♂️").font(.largeTitle)
} else{
shape.fill()
}
}
onTapGesture {
isFaceUp = !isFaceUp
}
}
}

Correct the syntax and it'll fix the crash:
struct CardView:View {
#State var isFaceUp:Bool = false
var body: some View {
ZStack{
let shape = RoundedRectangle(cornerRadius: 20)
if isFaceUp {
shape.stroke(lineWidth: 3)
Text("🧞‍♂️").font(.largeTitle)
} else{
shape.fill()
}
}.onTapGesture {
isFaceUp = !isFaceUp
}
}
}

Related

How to hide a swiftUI view by touching anywhere outside of it

I have a view that call an alert that is another smaller view, whenever the second View is shown, I want to hide it when clicking outside of it.
How can I do that?
struct AlertView: View{
let screenSize = UIScreen.main.bounds
#Binding var alertIsShown: Bool
var body: some View{
VStack{
Button("Cancel") {
self.alertIsShown=false
}
}.padding()
.frame(width: screenSize.width * 0.85, height: screenSize.height * 0.6)
.background(Color(red: 0.4627, green: 0.8392, blue: 1.0))
.clipShape(RoundedRectangle(cornerRadius: 20.0, style: .continuous))
.offset(y: alertIsShown ? 0 : screenSize.height)
.animation(.spring())
.shadow(color: Color(.white), radius: 6, x: -9, y: -0)
}
}
}
For the main view that call the alert:
struct MainView: View {
#State private var alertIsShown = false
#State var liveOrdersList: [String] = ["item-1", "item-2"]
var body: some View {
VStack{
NavigationView{
List{
ForEach(liveOrdersList, id: \.self) { order in
HStack {
VStack(alignment: .leading){
Text("\(order.totalPrice)")
}
Spacer()
Button("add") {
withAnimation(.linear(duration: 0.3)) {
alertIsShown.toggle()
}
}
}
}
}
}
}
}
if alertIsShown{ //here I call the aler
AlertView(alertIsShown: $alertIsShown)
}
}
The list of buttons call the alert view.
How can I hide it when tapping outside of it?
You could try this approach, using .simultaneousGesture(...), as shown in this example code, to hide the AlertView by touching anywhere outside of it.
struct ContentView: View {
var body: some View {
MainView()
}
}
struct MainView: View {
#State var alertIsShown = false
// for testing
#State var liveOrdersList: [String] = ["item-1", "item-2", "item-3", "item-4", "item-5"]
var body: some View {
NavigationView {
VStack {
List {
ForEach(liveOrdersList, id: \.self) { order in
HStack {
VStack(alignment: .leading) {
Text("\(order)")
}
Spacer()
Button("add") {
withAnimation(.linear(duration: 0.3)) {
alertIsShown.toggle()
}
}
}
}
}
// -- here
.simultaneousGesture(alertIsShown ? TapGesture().onEnded {
alertIsShown = false
} : nil)
if alertIsShown {
AlertView(alertIsShown: $alertIsShown)
}
}
}
}
}

How to pass UIImage between to 2 Views in SwifUI

I want to separate the views, and I created a struct view to get only the image to make the code more cleaner, please I want to ask how can I pass the selected image to the next view, to make the code more cleaner and Can use this struct view everywhere in the project.
I put in second view PickAPhoto() but I'm not sure how to get the data from it.
Many Thanks,
struct PickAPhoto: View {
#State var imgSelected: UIImage = UIImage(named: "addCameraImg")!
#State var showAddPhotoSheet = false
// phppicker begin
#State private var showPhotoSheet = false
// phppicker end
#State private var sourceType: UIImagePickerController.SourceType = .camera
#State var image: Image? = nil
var body: some View {
Image(uiImage: imgSelected)
.resizable()
.cornerRadius(4)
// .frame(width: 200 , height: 200)
.padding(.top)
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 200, maxHeight: 200)
.transition(.slide)
HStack {
Spacer()
Button(action: {showAddPhotoSheet.toggle()}) {
Label("Take a photo", systemImage: "camera")
.foregroundColor(Color("BrandPrimary"))
}
.sheet(isPresented: $showAddPhotoSheet){
ImagePicker(imageSelected: $imgSelected, sourceType: $sourceType)
// ImageViewPicker()
}
Button(action: { showPhotoSheet = true }) {
Label("Choose photo", systemImage: "photo.fill")
.foregroundColor(Color("BrandPrimary"))
}
.fullScreenCover(isPresented: $showPhotoSheet) {
PhotoPicker(filter: .images, limit: 1) { results in
PhotoPicker.convertToUIImageArray(fromResults: results) { (imagesOrNil, errorOrNil) in
if let error = errorOrNil {
print(error)
}
if let images = imagesOrNil {
if let first = images.first {
print(first)
// image = first
imgSelected = first
}
}
}
}
.edgesIgnoringSafeArea(.all)
}
Spacer()
}
}
}
struct SecondVIew: View {
var body: some View {
PickAPhoto()
}
func getImage(){
// I want to get the image here
}
}
Move the imgSelected source of truth state up in the View hierarchy and pass a write-access binding down.
struct SecondVIew: View {
#State var imgSelected: UIImage = UIImage(named: "addCameraImg")!
var body: some View {
PickAPhoto(imgSelected: $imgSelected)
}
}
struct PickAPhoto: View {
#Binding var imgSelected: UIImage

SwiftUI Change View with Edit Mode

I try to change the view state according to edit mode, when is editing hide the view and when it's not editing show the view, when I use on change and print the edit mode value its work but its doesn't work when working with views.
struct TaskStateMark_Row: View {
#ObservedObject var task: Task
#Environment(\.editMode) private var editMode
var body: some View {
Group {
// Show and hide the view according to edit mode state
if let editMode = editMode?.wrappedValue {
if editMode == .inactive {
taskState
.onTapGesture(perform: onTapAction)
}
}
}
}
private var taskState: some View {
Group {
if task.isCompleted {
completedState
} else {
incompletedState
}
}
.frame(width: 44, height: 44)
}
private var incompletedState: some View {
ZStack{
fillCircle
circle
}
}
private var circle: some View {
Image(systemName: "circle")
.font(.system(size: 24))
.foregroundColor(task.wrappedPriority.color)
}
private var fillCircle: some View {
Image(systemName: "circle.fill")
.font(.system(size: 24))
.foregroundColor(task.wrappedPriority.color.opacity(0.15))
}
private var completedState: some View {
Image(systemName: "checkmark.circle.fill")
.symbolRenderingMode(.palette)
.foregroundStyle(.white, task.wrappedPriority.color)
.font(.system(size: 24))
}
}
I found the answer in the developer documentation in the follow link
https://developer.apple.com/documentation/swiftui/editmode
The code
#Environment(\.editMode) private var editMode
#State private var name = "Maria Ruiz"
var body: some View {
Form {
if editMode?.wrappedValue.isEditing == true {
TextField("Name", text: $name)
} else {
Text(name)
}
}
.animation(nil, value: editMode?.wrappedValue)
.toolbar { // Assumes embedding this view in a NavigationView.
EditButton()
}
}

Dismiss a View in SwiftUI when parent is re-rendered

Using iOS14.4, Swift5.3.2, XCode12.2,
I try to dismiss a SwiftUI's GridView (see below code).
The dismiss function is done by the \.presentationMode property of the #Environment as explained here.
Everything works until the moment where I introduced a #Binding property that mutates the parent-View at the very moment of the dismissal. (see dataStr = titles[idx] in code excerpt below).
I read that dismissal by \.presentationMode only works if the parent-View is not updated during the time the child-View is shown.
But I absolutely need to cause a mutation on the parent-View when the user taps on an element of the GridView at play here.
How can I re-write so that parent-View is updated AND dismissal of Child-View still work ?
struct GridView: View {
#Environment(\.presentationMode) private var presentationMode
#Binding var dataStr: String
#State private var titles = [String]()
let layout = [
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
ScrollView {
LazyVGrid(columns: layout, spacing: 10) {
ForEach(titles.indices, id: \.self) { idx in
VStack {
Text(titles[idx])
Image(titles[idx])
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: (UIScreen.main.bounds.width / 2) - 40)
}
.onTapGesture {
// WITHOUT THIS LINE OF CODE - EVERYTHING WORKS. WHY???????????????
dataStr = titles[idx]
self.presentationMode.wrappedValue.dismiss()
}
}
}
.padding()
}
}
}
As #jnpdx asked, here you can see the parent-View. Please find the GridView(dataStr: self.$dataStr) inside the .sheet() of the ToolBarItem()....
import SwiftUI
struct MainView: View {
#EnvironmentObject var mediaViewModel: MediaViewModel
#EnvironmentObject var commService: CommunicationService
#State private var dataStr = ""
#State private var connectionsLabel = ""
#State private var commumincationRole: THRole = .noMode
#State private var showingInfo = false
#State private var showingGrid = false
init() {
UINavigationBar.appearance().tintColor = UIColor(named: "title")
}
var body: some View {
NavigationView {
if mediaViewModel.mediaList.isEmpty {
LoadingAnimationView()
.navigationBarHidden(true)
.ignoresSafeArea()
} else {
if dataStr.isEmpty {
MainButtonView(dataStr: $dataStr,
commumincationRole: $commumincationRole,
connectionsLabel: $connectionsLabel
)
.navigationBarHidden(false)
.navigationTitle("Trihow Pocket")
.navigationBarColor(backgroundColor: UIColor(named: "btnInactive"), titleColor: UIColor(named: "title"))
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: {
showingInfo.toggle()
}) {
Image(systemName: "ellipsis")
}
.sheet(isPresented: $showingInfo) {
InfoView()
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
showingGrid.toggle()
}) {
Image(systemName: "square.grid.3x3")
}
.sheet(isPresented: $showingGrid) {
// GRIDVIEW CALLING THE CHILD-VIEW IS HERE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
GridView(dataStr: self.$dataStr)
}
}
}
} else {
let str = self.dataStr
#if os(iOS)
PageViewiOS(dataStr: self.$dataStr, commumincationRole: $commumincationRole)
.navigationBarHidden(true)
.onAppear() {
if commumincationRole == .moderatorMode {
commService.send(thCmd: THCmd(key: .tagID, sender: "", content: str))
}
}
.ignoresSafeArea()
#elseif os(macOS)
PageViewMacOS()
.ignoresSafeArea()
#endif
}
}
}
.onTHComm_PeerAction(service: commService) { (peers) in
let idsOrNames = peers.map { (peer) -> String in
if let id = peer.id {
return "\(id)"
} else if let name = peer.name {
return "\(name)"
} else {
return ""
}
}
connectionsLabel = "Connected devices: \n\(idsOrNames.lineFeedString)"
}
.onTHComm_ReceiveCmd(service: commService) { (thCmd) in
if (commumincationRole == .moderatorMode) || (commumincationRole == .discoveryMode) {
switch thCmd.key {
case .tagID:
dataStr = thCmd.content
case .closeID:
dataStr = ""
default:
break
}
}
}
.onTHComm_LastMessageLog(service: commService) { (log) in
print(log)
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView()
.environmentObject(MediaViewModel())
.environmentObject(MultipeerConnectivityService())
}
}
With the help of #jnpdx, I found a workaround.
Wrap the binding-property (i.e. dataStr in my example) into a delayed block that executes after something like 50ms:
.onTapGesture {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50)) {
dataStr = thumNames[idx]
}
self.presentationMode.wrappedValue.dismiss()
}
Of course, this workaround only works in my case, because I do no longer need to keep the Child-View open. There might be other situations where the Parent-View needs to be updated prior to closing the Child-View (i.e. here the update of dataStr can be done right at the closing moment of the Child-View).
I am still wondering how to deal with dismiss-problems for any case where the Child-View makes the Parent-View update prior to closing. These are situations where SwiftUI's dismiss function no longer work from then on. Any mutation of the Parent-View cause the Child-View to separate somehow and dismisss no longer works.
Any idea what to do in that case ?

Need help to make a customEditMode as Environment

I like to re-build a customEditMode like same editMode in SwiftUI for learning purpose, I could made my code until a full error codes as possible, that was not my plan! However here is what I tried until now, need help to get this codes work. thanks
Update:
Why this Circle color does not change?
struct ContentView: View {
#Environment(\.customEditMode) var customEditMode
var body: some View {
CircleView()
VStack {
Button("active") { customEditMode?.wrappedValue = CustomEditMode.active }.padding()
Button("inactive") { customEditMode?.wrappedValue = CustomEditMode.inactive }.padding()
Button("none") { customEditMode?.wrappedValue = CustomEditMode.none }.padding()
}
.onChange(of: customEditMode?.wrappedValue) { newValue in
if newValue == CustomEditMode.active {
print("customEditMode is active!")
}
else if newValue == CustomEditMode.inactive {
print("customEditMode is inactive!")
}
else if newValue == CustomEditMode.none {
print("customEditMode is none!")
}
}
}
}
struct CircleView: View {
#Environment(\.customEditMode) var customEditMode
var body: some View {
Circle()
.fill(customEditMode?.wrappedValue == CustomEditMode.active ? Color.green : Color.red)
.frame(width: 150, height: 150, alignment: .center)
}
}
If you take a closer look at the editMode:
#available(macOS, unavailable)
#available(watchOS, unavailable)
public var editMode: Binding<EditMode>?
you can see that it is in fact a Binding. That's why you access its value using wrappedValue.
You need to do the same for your CustomEditModeEnvironmentKey:
enum CustomEditMode {
case active, inactive, none
// optionally, if you need mapping to `Bool?`
var boolValue: Bool? {
switch self {
case .active: return true
case .inactive: return false
case .none: return nil
}
}
}
struct CustomEditModeEnvironmentKey: EnvironmentKey {
static let defaultValue: Binding<CustomEditMode>? = .constant(.none)
}
extension EnvironmentValues {
var customEditMode: Binding<CustomEditMode>? {
get { self[CustomEditModeEnvironmentKey] }
set { self[CustomEditModeEnvironmentKey] = newValue }
}
}
Here is a demo:
struct ContentView: View {
#State private var customEditMode = CustomEditMode.none
var body: some View {
TestView()
.environment(\.customEditMode, $customEditMode)
}
}
struct TestView: View {
#Environment(\.customEditMode) var customEditMode
var body: some View {
Circle()
.fill(customEditMode?.wrappedValue == .active ? Color.green : Color.red)
.frame(width: 150, height: 150, alignment: .center)
VStack {
Button("active") { customEditMode?.wrappedValue = .active }.padding()
Button("inactive") { customEditMode?.wrappedValue = .inactive }.padding()
Button("none") { customEditMode?.wrappedValue = .none }.padding()
}
.onChange(of: customEditMode?.wrappedValue) { newValue in
print(String(describing: newValue))
}
}
}