简体   繁体   中英

How to auto-move/auto-adjust the save button for a note-taking app in swift?

This is what the app looks like when first opened:

在此处输入图片说明

I am content with this as the first screen the user sees.

The problem arises when the user hits the plus button to create a new note. The user touches the screen to add a new note and the keyboard covers up the save button at the bottom right corner. Is there any way to to have button appear above the keyboard on the top right side?

This is what it looks like:

在此处输入图片说明

I simply want the plus button above the keyboard on the right side so that the user can save the note. How would this be done?

Please help. Thank you!

Here is the entire code:

AppDelegate.swift:

import UIKit
import Firebase

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {



    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        FirebaseApp.configure()
        return true
    }

    // MARK: UISceneSession Lifecycle

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // Called when the user discards a scene session.
        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
    }


}

SceneDelegate.swift:

import UIKit
import SwiftUI

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

        // Create the SwiftUI view that provides the window contents.
        let contentView = ContentView()

        // Use a UIHostingController as window root view controller.
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = Host(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }

    func sceneDidDisconnect(_ scene: UIScene) {
        // Called as the scene is being released by the system.
        // This occurs shortly after the scene enters the background, or when its session is discarded.
        // Release any resources associated with this scene that can be re-created the next time the scene connects.
        // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        // Called when the scene has moved from an inactive state to an active state.
        // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
    }

    func sceneWillResignActive(_ scene: UIScene) {
        // Called when the scene will move from an active state to an inactive state.
        // This may occur due to temporary interruptions (ex. an incoming phone call).
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
        // Called as the scene transitions from the background to the foreground.
        // Use this method to undo the changes made on entering the background.
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        // Called as the scene transitions from the foreground to the background.
        // Use this method to save data, release shared resources, and store enough scene-specific state information
        // to restore the scene back to its current state.
    }


}

ContentView.swift:

import SwiftUI
import Firebase

struct ContentView: View {
    var body: some View {
        
        Home()
    }
}

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

struct Home : View {
    
    @ObservedObject var Notes = getNotes()
    @State var show = false
    @State var txt = ""
    @State var docID = ""
    @State var remove = false
    
    var body : some View{
        
        ZStack(alignment: .bottomTrailing) {
            
            VStack(spacing: 0){
                
                HStack{
                    
                    Text("Notes").font(.title).foregroundColor(.white)
                    
                    Spacer()
                    
                    Button(action: {
                        
                        self.remove.toggle()
                        
                    }) {
                        
                        Image(systemName: self.remove ? "xmark.circle" : "trash").resizable().frame(width: 23, height: 23).foregroundColor(.white)
                    }
                    
                }.padding()
                .padding(.top,UIApplication.shared.windows.first?.safeAreaInsets.top)
                .background(Color.red)
                
                if self.Notes.data.isEmpty{
                    
                    if self.Notes.noData{
                        
                        Spacer()
                        
                        Text("No Notes...")
                        
                        Spacer()
                    }
                    else{
                        
                        Spacer()
                        
                        //Data is Loading ....
                        
                        Indicator()
                        
                        Spacer()
                    }
                }
                
                else{
                    
                    ScrollView(.vertical, showsIndicators: false) {
                        
                        VStack{
                            
                            ForEach(self.Notes.data){i in
                                
                                HStack(spacing: 15){
                                    
                                    Button(action: {
                                        
                                        self.docID = i.id
                                        self.txt = i.note
                                        
                                        self.show.toggle()
                                        
                                    }) {
                                        
                                        VStack(alignment: .leading, spacing: 12){
                                            
                                            Text(i.date)
                                            
                                            Text(i.note).lineLimit(1)
                                            
                                            Divider()
                                            
                                        }.padding(10)
                                        .foregroundColor(.black)
                                    }
                                    
                                    if self.remove{
                                        
                                        Button(action: {
                                            
                                            let db = Firestore.firestore()
                                            
                                            db.collection("notes").document(i.id).delete()
                                            
                                        }) {
                                            
                                            Image(systemName: "minus.circle.fill")
                                            .resizable()
                                            .frame(width: 20, height: 20)
                                            .foregroundColor(.red)
                                        }
                                    }
 
                                }.padding(.horizontal)
                            }
                        }
                    }
                }
                
                
            }.edgesIgnoringSafeArea(.top)
            
            Button(action: {
                
                self.txt = ""
                self.docID = ""
                self.show.toggle()
                
            }) {
                
                Image(systemName: "plus").resizable().frame(width: 18, height: 18).foregroundColor(.white)
                
            }.padding()
            .background(Color.red)
            .clipShape(Circle())
            .padding()
        }
        .sheet(isPresented: self.$show) {
            
            EditView(txt: self.$txt, docID: self.$docID, show: self.$show)
        }
            
        .animation(.default)
    }
}

class Host : UIHostingController<ContentView>{
    
    override var preferredStatusBarStyle: UIStatusBarStyle{
        
        return .lightContent
    }
}

class getNotes : ObservableObject{
    
    @Published var data = [Note]()
    @Published var noData = false
    
    init() {
        
        let db = Firestore.firestore()
        
        db.collection("notes").order(by: "date", descending: false).addSnapshotListener { (snap, err) in
            
            if err != nil{
                
                print((err?.localizedDescription)!)
                self.noData = true
                return
            }
            
            if (snap?.documentChanges.isEmpty)!{
                
                self.noData = true
                return
            }
            
            for i in snap!.documentChanges{
                
                if i.type == .added{
                    
                    let id = i.document.documentID
                    
                    let notes = i.document.get("notes") as! String
                    
                    let date = i.document.get("date") as! Timestamp
                    
                    let format = DateFormatter()
                    
                    format.dateFormat = "MM/YY"
                    
                    let dateString = format.string(from: date.dateValue())
                    
                    self.data.append(Note(id: id, note: notes, date: dateString))
                }
                
                if i.type == .modified{
                    
                    // when data is changed...
                    
                    let id = i.document.documentID
                       
                    let notes = i.document.get("notes") as! String
                    
                    for i in 0..<self.data.count{
                        
                        if self.data[i].id == id{
                            
                            self.data[i].note = notes
                        }
                    }
                }
                
                if i.type == .removed{
                    
                    // when data is removed...
                    
                    let id = i.document.documentID
                    
                    for i in 0..<self.data.count{
                        
                        if self.data[i].id == id{
                            
                            self.data.remove(at: i)
                            
                            if self.data.isEmpty{
                                
                                self.noData = true
                            }
                            
                            return
                        }
                    }
                }
            }
        }
    }
}

struct Note : Identifiable {
    
    var id : String
    var note : String
    var date : String
}

struct Indicator : UIViewRepresentable {
    
    func makeUIView(context: UIViewRepresentableContext<Indicator>) -> UIActivityIndicatorView {
        
        let view = UIActivityIndicatorView(style: .medium)
        view.startAnimating()
        return view
    }
    
    func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<Indicator>) {
        
        
    }
}

struct EditView : View {
    
    @Binding var txt : String
    @Binding var docID : String
    @Binding var show : Bool
    
    var body : some View{
        
        ZStack(alignment: .bottomTrailing) {
            
            MultiLineTF(txt: self.$txt)
                .padding()
                .background(Color.black.opacity(0.05))
            
            Button(action: {
                
                self.show.toggle()
                self.SaveData()
                
                
            }) {
                
                Text("Save").padding(.vertical).padding(.horizontal,25).foregroundColor(.white)
                
            }.background(Color.red)
            .clipShape(Capsule())
            .padding()
            
        }.edgesIgnoringSafeArea(.bottom)
    }
    
    
    func SaveData(){
        
        let db = Firestore.firestore()
        
        if self.docID != ""{
            
            db.collection("notes").document(self.docID).updateData(["notes":self.txt]) { (err) in
                
                if err != nil{
                    
                    print((err?.localizedDescription)!)
                    return
                }
            }
        }
        
        else{
            
            db.collection("notes").document().setData(["notes":self.txt,"date":Date()]) { (err) in
                
                if err != nil{
                    
                    print((err?.localizedDescription)!)
                    return
                }
            }
        }
    }
}

struct MultiLineTF : UIViewRepresentable {
    
    
    func makeCoordinator() -> MultiLineTF.Coordinator {
        
        return MultiLineTF.Coordinator(parent1: self)
    }
    
    
    @Binding var txt : String
    
    func makeUIView(context: UIViewRepresentableContext<MultiLineTF>) -> UITextView{
        
        let view = UITextView()
        
        if self.txt != ""{
            
            view.text = self.txt
            view.textColor = .black
        }
        else{
            
            view.text = "Type Something"
            view.textColor = .gray
        }
        
        
        view.font = .systemFont(ofSize: 18)
        
        view.isEditable = true
        view.backgroundColor = .clear
        view.delegate = context.coordinator
        return view
    }
    
    func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<MultiLineTF>) {
        
    }
    
    class Coordinator : NSObject,UITextViewDelegate{
        
        var parent : MultiLineTF
        
        init(parent1 : MultiLineTF) {
            
            parent = parent1
        }
        
        func textViewDidBeginEditing(_ textView: UITextView) {
            
            if self.parent.txt == ""{
                
                textView.text = ""
                textView.textColor = .black
            }

        }
        
        func textViewDidChange(_ textView: UITextView) {
            
            self.parent.txt = textView.text
        }
    }
}

Remove your .edgesIgnoringSafeArea(.bottom) attached to the ZStack .

The safe area includes the area taken up by the keyboard. Right now, because you've explicitly told it to ignore the safe area, it's not moving to accommodate the keyboard. If it respects the safe area, the button will move with the keyboard.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM