Andy's iOS Journal

Day-28-StockPickerD7

  • May 22, 2021
  • Day 7 of the StockPickerChallenge

I haven’t had much time over the last couple of weeks to make progress.

The current version solves the basic challenge but I wanted to download stock history and have that get incorporated in a stockInfo vew. Unforutately, there is something simple that I am missing that I can’t quite get the date: closingValue information out of the json and into a [Double] rather than it coming back as a dict or some other object I haven’t figured out what.

Either way I got out of this what I wanted, I am going back to tutorial grinding and personal projects for a while.

Day-29-SwiftUI-and-spritekit

  • Jun 9, 2021
  • SpriteKit and SwiftUI

Objective for this morning is to create the code organization for a simple SpriteKit/SwiftUI App. I am going to use the code from Paul at HWS as a starting point.

There are really 2 problems that we need to address.

  1. How do we make a spritekit scene into a view that swiftui can use.
  2. How do we organize it so that it doesn’t end up all in a single file.

The first point is pretty well solved with the tutorial mentioned above and just works.

DropBoxView.swift

import SwiftUI
import SpriteKit

struct BoxDropView: View {

    var scene: SKScene {
            let scene = BoxDropScene()
            scene.size = CGSize(width: 300, height: 400)
            scene.scaleMode = .fill
            return scene
        }

    var body: some View {
        VStack {
            Text("Box Drop Game")
                .bold()
                .scaleEffect(CGSize(width: 2.0, height: 2.0))

            SpriteView(scene: scene)
                .frame(width: 300, height: 400)
                .ignoresSafeArea()
        }

    }
}

The main points here are that we define a scene variable that is of type SKScene. Inside this SKScene is an instance of the spritekit scene (BoxDropScene).

In the tutorial, that class is in the same file but it doesn’t have to be. So the code above by itself will error because we haven’t defined the BoxDropScene class

BoxDropScene.swift

This is pretty much from the tutorial example, It makes a box that falls when you tap. I decreased the size of the boxes to make it a little more interesting and gave it a physicsBody that is just slightly larger than the box so the boxes don’t actually touch.

import SpriteKit


class BoxDropScene: SKScene {
    override func didMove(to view: SKView) {
        physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let location = touch.location(in: self)
        let box = SKSpriteNode(color: SKColor.red, size: CGSize(width: 20, height: 20))
        box.position = location
        box.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width:22, height: 22))
        addChild(box)
    }
}

Folder organization

  • View
    • MainScreen.swift
    • BoxDropview.swift
  • SpriteKitScenes
    • BoxDropScene.swift

Down the road we may want the following for score data etc.

  • Model
  • ViewModel

MainScreen.swift

For the main screen, I am using NavigationLink and NavigationView to create a text button that will redirect the user to the BoxDropView() which contains the BoxDropScene written in SpriteKit.

import SwiftUI
import SpriteKit




struct MainScreen: View {
    var body: some View {
        NavigationView {
            ScrollView {

                NavigationLink(
                    destination: BoxDropView(),
                    label: {
                        Text("Box Drop Game")
                    })//NavigationLink

            }//ScrollView
        }//NavigationView
    }//Body
}//Struct End

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        MainScreen()
    }
}

With this template, we can now design the App in SwiftUI and wrap the SpriteKit into scenes that we can display using SwiftUI’s SpriteView. We could make any number of games and put them in a list or as cards that we can swipe through.

Day-29-SwiftUI-and-spritekit

  • Jun 9, 2021
  • Geometry reader

Next up is can I incorporate geometryReader into the SpriteView so that it fills up the available space? The main issue I was having is that when rotated, the scene was causing the boxes to elongate and weren’t boxes anymore as the scene was stretched. That won’t be good to go to production.

Solution

MainScreen.swift

  1. Created a @State variable in the MainScreen.swift
@Binding var geoSize: CGSize
  1. Added an .onRecive(notificationCenter) after Navigation view that updates the geoSize binding upon a device change orientation notification.

  2. Added a function that sets the Geometry size.

private func saveGeoSize(for geo: GeometryProxy){
    let size = CGSize(width: geo.size.width, height: geo.size.height)
    self.geoSize = size
    print(self.geoSize)
}
struct MainScreen: View {
    @State var geoSize = CGSize(width: 30, height: 40)
    @State var reOrient = false

    var body: some View {
        GeometryReader { geo in
                NavigationView {
                    ScrollView {

                        NavigationLink(
                            destination: BoxDropView(geoSize: $geoSize, reOrient: $reOrient),
                            label: {
                                Text("Box Drop Game")
                            })//NavigationLink

                    }//ScrollView
                }//NavigationView
                .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
                    self.reOrient = true
                    self.saveGeoSize(for: geo)

                }
        } //GeometryReader
    }//Body

    private func saveGeoSize(for geo: GeometryProxy){
        let size = CGSize(width: geo.size.width, height: geo.size.height)
        self.geoSize = size
        print(self.geoSize)
    }
}//Struct End

BoxDropView

  1. added a @Binding
@Binding var geoSize: CGSize

  1. Updated the scene.size and SpriteView().frame variables
scene.size = CGSize(width: self.geoSize.width*0.9, height: geoSize.height*0.9)
SpriteView(scene: scene)
                                .frame(width: geo.size.width*0.9, height: geo.size.height*0.9)
                                .ignoresSafeArea()

references

Remaining issues

  • The scene resets every time the notification is activated. The binding updates which the updates the scene by creating a new BoxDropScene, which is a class so all children (boxes) get reset every time you reorient. I could just force it to be in one or the other mode but I am curious to see what other options are possible. I suppose I could also make it so upon rotation the frame doesn’t change from the original size. But both of these are not terribly satisfying.

Day-29-Shared-Instances

  • Jun 9, 2021
  • Shared instances

Shared instances appear to solve most of the issue I am having but not fully.

Solution

  1. Add a shared instance to the BoxDropScene class
class BoxDropScene: SKScene {
    static let shared = BoxDropScene()
  1. Change the instance of the BoxDropScene upon creation in BoxDropView.swift from
var scene: SKScene {

        let scene = BoxDropScene()

to

var scene: SKScene {
        let scene = BoxDropScene.shared

Remaining Issues

The children in the box can be lost when going from landscape to portrait and the State variable is in the mainScreen, which means the geoSizes (portrait or landscape) is set on that screen.

Question

Can I keep users from switching after entering the game? – Looks like this is possible though may be slightly more complicated to pull off. See SwiftUI: Force orientation on a per screen basis
for more details.

References

spritekit-transition-between-scenes-without-resetting-game

I think if I make a game I will just decide to have it in one orientation if I am going to

Day-30-LazyStacks

  • iOS Foundations Module 4 Lesson 8 LazyStacks
  • Aug 1, 2021

Wow so after almost 3 months of me exploring some side projects and life getting in the way (hernia surgery etc – all good now). I am finally getting back into the tutorials for module 4. It is a bit challenging to start off where I left off.

I had actually explored LazyStacks in one of my side projects. I listed the alphabet and had it slide horizontally rather than vertically.

Anyway, I am back, and will be plugging away at these tutorials again. Super excited to see what the database and design courses have to offer. I may skip city module and come back to it later.

The skills I really need to pick up in order to get my own ideas off the ground are

  • CoreData
  • CloudKit

I want to also look into ResultBuilder and making my own modules that I can load for reusable code.

Day-31-Picker

  • iOS Foundations Module 4 Lesson 9 Picker
  • Aug 3, 2021

I didn’t realize that the scrolling wheel that many apps use and often times adds haptics too was called Picker

@State var location = 1

Picker("location", selection: $location) {
     Text("a").tag(1)
     Text("b").tag(2)
     Text("c").tag(3)
}

I wonder if the picker can

  • Be shown horizontal instead of a vertical wheel.
  • Be changed in size so that more of the wheel can be shown,
    • A diameter parameter would be neat

If this is possible it would be possible to set it up like a LazyHStack but have tags that make it easier to jump to specific positions.

Day-32-Portions

  • iOS Foundations Module 4 Lesson 10 and 11
  • Aug 6, 2021

Technically, I started the challenge in lesson 9 but was having trouble staying motivated to do it. So I looked at the answer and continued my momentum forward. Picker is quite versatile.

Lesson 10 just covered how to calculate a portion for the recipe app. Lesson 11 started the implementation. It covered static functions in classes and also implemented a picker in the app.

Day-33-Portion-Calc

  • iOS Foundations Module 4 Lesson 12
  • Aug 13, 2021

For those that are juggling learning swift on the side while maintaining a life and a full time job. This is your reminder, that it is ok to continue to plug away at these tutorials and your own projects in the time you find or carve out. As you can see, I am not able to actively work on swift learning or my projects every day.

This lesson actually implemented the calculate the portion that was described in lesson 9/10.

One nice nugget that is in this lesson is in line if statements.

A variable can be reassigned depending on a conditional.

newvariable = (conditional statement) ? "than this" : "else this"

Examples

portion += wholePortions > 0 ? " " : ""

portion += ingredient.num == nil && ingredient.denom == nil ? "" : " "

Day-33-Featured-Details

  • iOS Foundations Module 4 Lesson 13
  • Aug 13, 2021

Chris covered a lot in this lesson. At times it seemed pretty fast and I had to pause it to get caught up. Some of that is probably because I am coming back to this project after a lot of time and am not holding it all in my head.

Some interesting tidbits:

  • A button can be placed around anything and a .sheet modifier can be used to tell it where to go.
.sheet(isPresented: $isDetailViewShowing){
      // Show recipe Detail View
      RecipeDetailView(recipe: model.recipes[index])
  }
  • When a @State variable needs to be updated the init method wont’ work but the .onAppear method can be used.
.onAppear(perform: {
            setFeaturedIndex()
        })

I can see how the .onAppear method could also be helpful when updating other variables.

A quick search revealed that there are other methods for [gestures](Apple Developer Documentation too!

Day-33-Custom-Fonts

  • iOS Foundations Module 4 Lesson 14
  • Aug 13, 2021

This was a short and useful tutorial on how to add custom fonts.

How to use custom fonts in swift

System Fonts ready to use

.font(Font.custom("Avenir Heavy",size: 24))

Done with Module 4!

Day-33-34-35-36-Core-Data

  • IosDatabases (Swift UI) Module 5

Core data is one of the libraries critical for most of the apps I want to build. I have spent the last couple of weeks working my way through Core data and now have a pretty good grasp of what it is and does. I have one more lesson to go through.

Lesson 1: Basics

Benefits

  • First Party Framework
    • will not be left behind
    • works with other Apple APIs/frameworks
  • Not 3rd party library

Drawbacks

  • Not for remote data storage
  • There is learning curve
  • Object graph persistent framework

Graph Persistent framework

Core data manages the SQL-lite database for you behind the scenes.

  • Object Graph in Memory
  • Managed Object Context
    • data management layer (handles saving loading etc)
  • Core data Persistant container

Methods

  • NSManagedObject can be serialized and stored in CoreData
  • Classes are defined as Entities
  • Properties are Attributes

Core data will automatically generate classes for you using a GUI.

  • the file that is labeled filename.xcdatamodeld
    • contains the gui for setting up managed classes (Entities).

Lesson 2

  • Define entities and attributes
  • Generate the classes in the model
  • Get a reference to the Core Data persistent container
  • Get the managed object context

Codegen property in the Entity Class property section (right side window)

  • Manual/None

    • Editor->Create NSManagedObject subclass
      • generates 2 files
        • Filename+CoreDataClass.swift
          • add methods in this file
        • Filename+CoreDataProperties.swift
          • at this time try not to add anything to this file.
  • Class definition

    • No files are generated, no class extension possible. These files will only be generated in the product
  • Category/Extension

    • if you aren’t going to add anything to the second file Filename+CoreDataProperties.swift then let XCode just generate it in the product for you. The first file will be generated for you to edit Filename+CoreDataClass.swift

How to add core data after the fact

  1. Add new file → CoreData (Data Model)
  • save with the same name as your project
  1. Add Code to Persistance.swift
  • Make sure the persistant container has the right name
  • take code from a fresh project that you added core data from the start.
  1. add Entities and Attributes

Configuring Entities:

Lesson 3

  • Aug 21, 2021
  • Added
  • Delete
  • Update
  • Save

File Prep

After I started a new project with Core data it added a bunch of code that can be commented out.

Persistence.swift

Comment out the following blocks of code as it only applies to previews as part of the example Apple provides

//    static var preview: PersistenceController = {
//        let result = PersistenceController(inMemory: true)
//        let viewContext = result.container.viewContext
//        for _ in 0..<10 {
//            let newItem = Item(context: viewContext)
//            newItem.timestamp = Date()
//        }
//        do {
//            try viewContext.save()
//        } catch {
//            // Replace this implementation with code to handle the error appropriately.
//            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//            let nsError = error as NSError
//            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
//        }
//        return result
//    }()
//        if inMemory {
//            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
//        }

Environment variable

The environment variable at the top of ContentView.swift sets the variable for the managedObjectContext. IN our case, this variable is named viewContext. But should be placed at the top of your view hierarchy.

@Environment(\.managedObjectContext) private var viewContext

How to create a Person object

let p = Person(context: viewContext)

Fetch all Person objects and put into var people

  • put this near Environment variables
@FetchRequest(sortDescriptors: []) var people: FetchedResults<Person>

Previews in ContentView

I was able to get the preview working by changing the .preview to .shared.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environment(\.managedObjectContext, PersistenceController.shared.container.viewContext)
    }
}

Save

It is important that after any action (Add, Delete, Update) that the save method is called.

do {
    try viewContext.save()
} catch  {
    // error with saving
}

Add

private func addItem() {

    let p = Person(context: viewContext)
    p.age = 20
    p.name = "Joe"

}

Delete

Where person is a Person objects from the array persons

viewContext.delete(person)

Update

You can modify the attributes in any of the objects in the array of class objects and the call save.

person.name = "Ted"

Lesson 4 Sorting and Filtering with Core Data.

  • Aug 28, 2021
  • NSSortDescriptor: sorting
  • NSPredicate: Filtering

The FetchRequest described above can be sorteded and filtered using NSSortDescriptor and NSPredicate

  • sortDescriptors: [NSSortDescriptor(key: “age”, ascending: true)] is an array so you can add more than 1 NSSortDescriptor
  • predicate: NSPredicate(format: “age > 9 && age < 16”)) can be used to filter based on attributes in the core data.
  • var people is where you are setting the fetch request.

If you have set filters you want to apply to data with no user input you can put those filters/sorts directly in the fetch request as follows.

    @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "age", ascending: true)], predicate: NSPredicate(format: "age > 9 && age < 16")) var people: FetchedResults<Person>

More often however, we want to be able to give the user the ability to sort and filter. In order to do this we need to create some @State variables and update those State variables using .onchange modifiers associated with Text or Stacks.

@State var people = [Person]()
@State var filterByText = ""




VStack {
  // Textfield for filter
  TextField("Filter Text", text: $filterByText) { _ in
      // Fetch new data
      // Gets activated upon mouse insertion into text field and upon enter
      //fetchData()
  } // List of people you fetched
  List {
               ForEach(people) { person in
               }


  }
  .onChange(of: filterByText, perform: { value in
    fetchData()
  })

} // End of VStack

func fetchData() {
    //Create fetch request
    let request = Person.pfetchRequest()
    //Set  sort descriptors and predicates
    request.sortDescriptors = [NSSortDescriptor(key: "age", ascending: true)]
    // Don't need single quotes for %@
    request.predicate = NSPredicate(format: "name contains %@", filterByText)
    //Execute the fetch on the main thread so it updates right away
    DispatchQueue.main.async {
        do {

            let results = try viewContext.fetch(request)
            // Update the state property
            self.people = results
        } catch {

        }
    }

}
``

Day-38-Core-Data

  • Sep 6, 2021
  • entities and relationships

Relationships can be set between entities in your core model.

  • Person Entity
  • Family Entity

A family entity can contain several person entities. These kinds of relationships are identical to tables in a database (A beginner's guide to database table relationships - Vlad Mihalcea) with one to one, one to many and many to many relationship possibilities.

Apple recommends that for the integrity of the object graph then when making a relationship is to create the inverse relationship.

So basically, Core Data can be viewed as Apples library for creating database tables and providing all the functions inherent with using databases to save, delete, update, filter and sort data. The tables in the database are the entities and setting up entities well will result in a more efficient utilization and exploration of your data.

Examples:

Person

  • Country
  • Person
  • Family

Post

  • Post
  • post_content

Products/stores

Day-39-40-Core-Data-Application

  • Oct 6, 2021
  • Core Data

So as many of you know, most app ideas I have come up will function better with a Core Data backend. Using what I have learned so far I have been trying to incorporate. I thought the database module on core data would be sufficient but it only provides the ground work. After several weeks of struggling to implement it in my own app, I remembered the next section in CWC+ was Chris’s refactoring of the Recipe App to use Core data. Since I wanted to refactor my own App, I used that as a template. I just downloaded the final project’s code and used that updating my project as needed.

Here are a few things I learned

  1. In the Core Data model, I didn’t have to use Transformable for a table or list of items. Instead I created a relationship similar to the one where a Recipe entity can have multiple Ingredient Entities. By making a relationship between Recipe and Ingredient, Core data can have a recipe with many ingredients one to many.
    a. In order to do this, you will want to make an Ingredient Class in the Recipe.swift file in addition to your Recipe Class.

  2. I decided to just store the URI (URL) for any images I need to store in Core Data. Images can be large and take up a lot of memory

  3. It is not possible to do everything in the Persistence.swift class
    a. DataServices.swift contains the JSON decoding getLocalData() function
    b. RecipeModel.swift contains the checkLoadedData() function which is super useful when you only want to load the data once. Though be careful not to forget about this variable. I have added something else into the RecipeModel.swift’s preloadLocalData() function which only run’s if checkLoadedData() hasn’t already run. In this case, it is easier to just delete the program from the simulator and start again so it will be the first time run. I could probably set a button or something that resets it but this was just easier without code. It is the preloadLocalData() function that we convert the json decoded data and load it into Core Data Entities.

Anyway, having the Recipe App as an example really sped up my integration of Core data into my own app. Examples are so useful!

Day-41-Core-Data-previews

I spent a good couple of hours getting previews working with my core data info. Not sure why it works so well for Chris in the Recipe App. But if you start running into errors here is an example from a UserView I am working on where a person can add a User profile.

struct UserView_Previews: PreviewProvider {
    static var previews: some View {
       // UserView()
        let request = User.ufetchRequest()
        let users = try! PersistenceController.shared.container.viewContext.fetch(request)
        UserView(u: users[0])
    }
}

Day-42-Core-Data-StateVariables

  • Oct 19, 2021

So I was feeling pretty good about all this Core Data progress I had made and then tried to make a User page where a user could create a small bio, add a birthdate etc. I create a simple default one for the user but want the user to be able to change it to their own information.

@State variables seem like a logical choice but it turns out even if your view struct has a variable for a User object which contains variables bio, dateOfBirth, name, photo etc, we can’t access those variables until self has been created! What a frustrating error!

Basically I can’t just do this

@State var biography = u.bio!

The way to get around this error is to create an init() function that then can set the @State variables.

@Environment(\.managedObjectContext) private var viewContext
    var u:User
//    @Environment(\.managedObjectContext) private var viewContext
    @State var isModal: Bool = false
    @State var name = "Default_"
    @State var biography = ""//u.bio!
    //@State var dob =
    @State var userimage = Image(systemName: "person.crop.square")
    // in order for the @State variables to communicate with core data need to initialize all of the @State Variables using the the stored core data User object.  See https://developer.apple.com/forums/thread/128366
    init(u:User) {
        // need to add an if statement that will set the image using the URL if present in the User object
        self.userimage =  Image(systemName: "person.crop.square") //Image(u.photo
        self.name = u.name!
        self.biography = u.bio!
        self.u = u  // this has to be set last for some reason
        
        
    }

With this setup, the @State Variables are instantly updated as you would expect and I set the core data save function to save upon disappear of the sheet.

  • NOTE: It is critical that you set self.u = u last, for some reason if you set it first and try to set the other variables it doesn’t work properly. I believe the issue is that self.u = u will overwrite all the other u.parameters because these are state properties and the @State property is defined above the init() function. Yeah that doesn’t quite follow because my save function in the .sheet is onDisappear. What I know is that it works if I put it last. :slight_smile:

  • See swiftUI:how to resolve "Cannot use… | Apple Developer Forums

Now that I have figured out core data basic app setup, previews, core data @State variables, I am able to focus on building the rest of the App. Back to the creative part of our work: UI Experience.

Day-43-and-44-Encoding

  • Oct 24, 2021
  • writing and reading files

There is some strange complexity around combing Core data with SwiftUI’s @environment/@State variables. I will have to create a simple example some time but the gist of it is that I wanted to pass a selected user to several subviews. If the selected user is possibly one of many users then I would have to have a variable that stores the index of the selected user from an array of users. The selected user variable could be put in a Constants Observable class and be @Published. In addition, the Users would need to be fetched from core data in each subview.

Anyway, my code wasn’t working so I just gave each subview an initiation variable with subView(selecteduser: user and passed it down the views. The App I am exploring isn’t all that overly complicated and it all seems to be working. There is just a part of me that feels there is probably a better way.

Also, discovered a common problem and didn’t understand why saved files weren’t found every time I recompiled

Turns out:

 Documents Directory changes every time I recompile so 

So to get around this it is important to get the documentDirectory independent of the filename and store the filename separately as that doesn’t change.

In linux it would be like have your project folder directory location change every time you rebooted the computer. The contents of your folder are all the same but you don’t know where that folder is every time you reboot!

I have in my Core Data model a variable that stores the location of a swift object as URL (URI) but in reality it could probably just be stored as a string and then converted to URL. URL("filename"). Fortunately, I don’t have to change the model if I don’t want to as URL objects have .absouteString conversionfunctions

This was grabbed from Hacking with swift but have seen several variations around the internet and know I understand why! (to overcome the fact that the documents directory changes on every recompile)

    func getDocumentsDirectory() -> URL {
        // find all possible documents directories for this user
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)

        // just send back the first one, which ought to be the only one
        return paths[0]
   
    }

Makes me wonder about some older projects where I have been concerned about saved files being lost may have in fact have just been this recompile issue.

So do I need to integrate CloudKit into my app or should I encourage users to be diligent with their data by backing up their phones? What do you think people are looking for in apps where they are storing data?

Useful references

1 Like

Hey Andy! good question… my solution would be to write it both ways just to make sure I could figure it out, and then see which one integrates better in the UI. losing the data should be rare? I could be wrong

#Day-45-dragDownView-fullScreenCover

It has probably been several days. I have been working on an app and want to use the .fullScreenCover pop over instead of .sheet. But when I do this, I have to have a button to dismiss it. I didn’t want a button, I just want to drag down from the top of the screen. I have seen this in many apps. I got everything to work except that I don’t see the the main screen as I drag, I just see white. I don’t know how to make that transparent to see the original view or if this is just impossible ATM in SwiftUI. Anyone willing to take a stab at this? I have example code below, should just be able to paste it directly into a new project for it to work.

//
//  ContentView.swift
//  dragDownExample
//
//  Created by Andy Wander on 11/1/21.
//

import SwiftUI
import CoreData

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext
    @EnvironmentObject var selected: Selected
    
    @State var isModal: Bool = false
    
    var body: some View {
        VStack{
            Text("Press Button")
            Button(action: {
                self.isModal = true
            }) {
                ZStack {
                    Rectangle().foregroundColor(.green)
                    Text("activate Full Sheet Cover")
                    
                }
            }
            .fullScreenCover(isPresented: $isModal, onDismiss: { }, content: {
                fullCoverView()
            }).environmentObject(Selected())
            
            
        }
    }
    
}

// MARK: fullCoverView Yellow background
struct fullCoverView: View {
    @EnvironmentObject var selected: Selected
    
    var body: some View {
        ZStack{
            Rectangle().foregroundColor(.yellow)
            VStack{
                dragDownView()
                Spacer()
                Text("Full Sheet cover")
                Spacer()
            }
            //Using the selected.SheetOffset I can move the fullCover down by the value of the offset
        }.offset(x: 0, y: CGFloat(selected.sheetOffset))
    }
    
}

// MARK: Utilities Selected sheetOffset
class Selected: ObservableObject {
    // this is the offset value that is tracked
    @Published var sheetOffset:Float = 0.0

}

// MARK: DragDown View bar at top
struct dragDownView: View {
    
 //   @State var  selected.sheetOffset:Float
   // @Binding var isModal:Bool
    @Environment(\.presentationMode) var isModal
    @EnvironmentObject var selected: Selected

    var body: some View {
    ZStack {
        //This rectangle is a shadow rectangle for better accuracy in user grabbing the smaller rectangle
        RoundedRectangle(cornerRadius: 30.0)
            .frame(width: 200, height: 20, alignment: .center)
            .opacity(0.01)
            .gesture(DragGesture(minimumDistance: 10, coordinateSpace: .global) // minimum distance is distance to activate gensture
                // swipe down to be an animated dismissal
                        .onChanged({ value in
                            // Don't let the user offset the sheet above 0
                            if selected.sheetOffset < 0 {  // set an @environmentobject variable in the sheet above to offset the sheet based on drag distance  But haven't figured out how to show the main screen
//                                                sheetOffset = 15
                            } else {
                                selected.sheetOffset = Float(value.translation.height)
                            }

                        })
                        .onEnded { value in
                            // reset offset again on ending of drag if <100
                            if (selected.sheetOffset <= 100) {
                                selected.sheetOffset = 0
                            }
                          
                                let verticalAmount = value.translation.height as CGFloat
                                let startValue = value.startLocation.y as CGFloat
                                if (verticalAmount > 100 && startValue < 100) {
                                    isModal.wrappedValue.dismiss()  // dismiss the full sheet cover
                                    selected.sheetOffset = 0  // reset the sheetOffset to 0
                                    print("down swipe")
                                    
                                }
            })
        // smaller gray rectangle
        RoundedRectangle(cornerRadius: 30.0)
            .foregroundColor(.gray)
            .frame(width: 50, height: 5, alignment: .center)
            .padding([.top,.bottom])
            
    }//Zstack
     .shadow(radius: 10)
    } // var body
}





struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}

@mikaelacaron @Chris2 @Chris_Parker thoughts?

Good question but I don’t know what the answer is unfortunately.

What’s this thing supposed to do? I think it works for me.

  1. I click the blue “activate Full Sheet Cover” on a green background, and
  2. it brings up a yellow background “Full Sheet cover”.
  3. I grab the little handle near the top and pull down, exposing white underneath.
  4. Then the yellow sheet disappears, reappears, and animates down and away.
  5. I’m left with the green screen and can go back to #1.

Latest Xcode and MacOS.

1 Like

Exactly, the dragDownView can be used on any of your views you have written for a drag down dismissal. It is close to what I have seen with other apps. The difference is when you drag down, I was hoping to be able to see the first view (green background) instead of plain white during a partial drag before it is dismissed.

1 Like