0

The following code ran fine on iOS 14.0, but now on iOS 15.0, causes a crash.

var noteFile: NoteFile!

func saveToNoteFile(completion: ((Bool) -> Void)?) {
            let mutableAttrs = NSMutableAttributedString(attributedString: textView.textStorage)
      
        noteFile.attrs = mutableAttrs

        noteDescriptor.overview = (noteFile.attrs.string as NSString).substring(with: NSMakeRange(0, min(150, noteFile.attrs.length)))
        
        self.noteDescriptor.title = String(noteFile.attrs.string.split(separator: "\n").first ?? "" )
        
        let images = NEFileManager.getImagesFromAssetFolder(noteId: noteDescriptor.id)
        for (idx, image) in images.enumerated() {
            if idx == 0 { noteDescriptor.descImage1 = image }
            else if idx == 1 { noteDescriptor.descImage2 = image }
            else { break }
        }

        noteFile.save(to: noteFile.fileURL, for: .forOverwriting) { (success) in //Crash occurs HERE.
            completion?(success)
            print("saved", self.noteDescriptor.title)
        }
    }

The error message is as follows:

NSURL URLByAppendingPathExtension:]: component, components, or pathExtension cannot be nil

What does Xcode mean by 'components' and how can I ensure this variable is not nil?

Thanks.

Edit: NoteFile object below.

    class NoteFile: UIDocument {
    private var noteId: String!
    private let name = "note"
    var attrs: NSMutableAttributedString!
    
    init(noteDescriptor: NoteDescriptor) {
        self.noteId = noteDescriptor.id
        let url = NEFileManager.noteFromAssetFolder(id: noteId)?.appendingPathComponent(name)
        super.init(fileURL: url!)
    }
    
    // MARK: I/O Operations
    
    override func load(fromContents contents: Any, ofType typeName: String?) throws {
        if let fileWrapper = contents as? FileWrapper {
            if let textFileWrapper = fileWrapper.fileWrappers![name] {
                if let data = textFileWrapper.regularFileContents {
                    let string = String(data: data, encoding: .unicode)!
                    self.attrs = NSMutableAttributedString(string: string)
                    self.attrs.addAttribute(.font, value: UIFont.systemFont(ofSize: 16), range: NSRange(location: 0, length: string.count))
                }
            }
        }
    }
    
    override func contents(forType typeName: String) throws -> Any {
        let contentsFileWrapper = FileWrapper(directoryWithFileWrappers: [:])
        if let data = self.attrs.string.data(using: .unicode) {
            let textFileWrapper = FileWrapper(regularFileWithContents: data)
            textFileWrapper.preferredFilename = name
            contentsFileWrapper.addFileWrapper(textFileWrapper)
        }
        return contentsFileWrapper
    }
    

NEFileManager class

import UIKit

public class NEFileManager {
    
    // query app's asset folder path and create  if not existed
    class func assetFolderURL() -> URL? {
        do {
            var path = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
            path = path.appendingPathComponent(APP_DOCUMENT_ASSET_FOLDER)
            var isDir : ObjCBool = false
            if FileManager.default.fileExists(atPath: path.path, isDirectory: &isDir) {
                if isDir.boolValue {
                    return path
                }
            }
            try FileManager.default.createDirectory(at: path, withIntermediateDirectories: false, attributes: nil)
            return path
            
        } catch (let error) {
            print(error)
        }
        return nil
    }
    
    class func noteFromAssetFolder(id: String) -> URL? {
        guard let assetUrl = assetFolderURL() else { return nil }
        do {
            let path = assetUrl.appendingPathComponent(id)
            var isDir : ObjCBool = false
            if FileManager.default.fileExists(atPath: path.path, isDirectory: &isDir) {
                if isDir.boolValue {
                    return path
                }
            }
            try FileManager.default.createDirectory(at: path, withIntermediateDirectories: false, attributes: nil)
            return path
        } catch (let error) {
            print(error)
        }
        return nil
    }
    
    public class func deleteNoteFromAssetFolder(id: String) -> Bool {
        guard let noteFolderURL = noteFromAssetFolder(id: id) else { return false }
        do {
            try FileManager.default.removeItem(at: noteFolderURL)
            return true
        } catch (let error) {
            print(error)
        }
        return false
    }
    
    class func createdDateOfFile(path: String) -> NSDate? {
        do {
            let attrs = try FileManager.default.attributesOfItem(atPath: path)
            return attrs[FileAttributeKey.creationDate] as? NSDate
        }catch (let error) {
            print(error)
        }
        return nil
    }
    
    class func modifiedDateOfFile(path: String) -> NSDate? {
        do {
            let attrs = try FileManager.default.attributesOfItem(atPath: path)
            return attrs[FileAttributeKey.modificationDate] as? NSDate
        }catch (let error) {
            print(error)
        }
        return nil
    }
    
    class func writeImageToAssetFolder(noteId id: String, image: UIImage, fileName: String) -> Bool {
        guard let noteFolderURL = noteFromAssetFolder(id: id) else { return false }
        var filePath = noteFolderURL.appendingPathComponent(fileName)
        filePath = filePath.appendingPathExtension("jpg")
        do {
            if let imageData =  UIImageJPEGRepresentation(image, 1.0) {
                try imageData.write(to: filePath, options: .atomic)
                return true
            }
        } catch (let error){
            print(error)
        }
        return false
    }
    
    class func getImagesFromAssetFolder(noteId id: String) -> [String] {
        do {
            guard let noteFolderURL = noteFromAssetFolder(id: id) else { return [String]() }
            let files = try FileManager.default.contentsOfDirectory(atPath: noteFolderURL.path)
            var images = [String]()
            for file in files {
                if (file as NSString).pathExtension == "jpg" {
                    images.append(file)
                }
            }
            return images
        } catch (let error) {
            print(error)
        }
        return [String]()
    }
    
    class func writeDataToAssetFolder(noteId id: String, data: Data, fileName:String) -> Bool {
        guard let noteFolderURL = noteFromAssetFolder(id: id) else { return false }
        let filePath = noteFolderURL.appendingPathComponent(fileName)
        do {
            try data.write(to: filePath, options: .atomic)
            return true
        }catch (let error){
            print(error)
        }
        return false
    }
    
    class func moveFileToAssetFolder(noteId id: String, sourceUrl: URL, fileName: String) -> Bool {
        guard let noteFolderURL = noteFromAssetFolder(id: id) else { return false }
        let filePath = noteFolderURL.appendingPathComponent(fileName)
        do {
            if !FileManager.default.fileExists(atPath: filePath.path) {
                try FileManager.default.moveItem(at: sourceUrl, to: filePath)
            }
            return true
        }catch (let error){
            print(error)
        }
        return false
    }
    
    class func getImageFromAssetFolder(noteId id:String, image name: String) -> UIImage? {
        guard let noteFolderURL = noteFromAssetFolder(id: id) else { return nil }
        let imagePath = noteFolderURL.appendingPathComponent(name)
        do {
            if FileManager.default.fileExists(atPath: imagePath.path) {
                let data = try Data(contentsOf: imagePath)
                return UIImage(data: data)
            }
        } catch (let error) {
            print(error)
        }
        
        return nil
    }
    
    class func getFileURLFromAssetFolder(noteId id:String, file name:String) -> URL? {
        guard let noteFolderURL = noteFromAssetFolder(id: id) else { return nil }
        let filePath = noteFolderURL.appendingPathComponent(name)
        if FileManager.default.fileExists(atPath: filePath.path) {
            return filePath
        }
        return nil
    }
}
gcharita
  • 7,729
  • 3
  • 20
  • 37
  • What is the value of `noteFile.fileURL` when it crashes? – Phillip Mills Nov 06 '21 at 23:17
  • @PhillipMills It is: "file:///Users/user/Library/Developer/CoreSimulator/Devices/7F00DB93-4345-440E-AAA3-A45082E603D3/data/Containers/Data/Application/802E36E4-1D5E-4012-B237-180A3FD1E164/Documents/.asset/EA3D145E-E6B9-4625-ABB5-A310BDF8B25B/note" – user2312844 Nov 07 '21 at 01:26
  • I am starting to think it's because I am on a M1 MacBook, I don't remember seeing this occur on my old machine. And I downloaded iOS 14 simulator and the crash still occurs. I run it in rosetta to prevent any weirdness due to the architecture so who knows. – user2312844 Nov 07 '21 at 01:30
  • You haven't provided nearly enough information. Where is the `NoteFile` type defined? Where does your `noteFile` variable get assigned a value? – Duncan C Nov 07 '21 at 01:46
  • ...and on what line in the code is the error raised? – Scott Thompson Nov 07 '21 at 01:54
  • By "component/components" the error is referring to the different parts that make up a path. So a path like "/pineapple/bedroom/sponge_bob", "pineapple" "bedroom" and "sponge_bob" are path "components". The same error is probably used if you execute `URLByAppendingPathComponent:isDirectory:` – Scott Thompson Nov 07 '21 at 01:58
  • @ScottThompson Okay that's helpful, but the path doesn't look weird to me. I could try testing on device but normally it always works on simulator. There line the error is raised is: `noteFile.save(to: noteFile.fileURL, for: .forOverwriting) { (success) in //Crash occurs HERE.` – user2312844 Nov 07 '21 at 02:20
  • @DuncanC Sorry yeah I didn't think the initializer would be important but perhaps you're right since I now see that the error is about fileURL. I updated OP to include the full class. – user2312844 Nov 07 '21 at 02:23
  • What is an example value for `fileURL` when the code crashes? Does it include a file type extension? – Scott Thompson Nov 07 '21 at 02:27
  • @ScottThompson Printing description of self.noteFile.some._fileURL: (NSURL?) _fileURL = "file:///Users/user/Library/Developer/CoreSimulator/Devices/83176304-1BB4-43C1-B37E-B3C1A3AAAF35/data/Containers/Data/Application/488D4A1D-91B6-446E-BF2C-8262FF3BB801/Documents/.asset/6C122A78-A817-4055-BBCD-06B6438528E5/note" – user2312844 Nov 07 '21 at 02:30
  • Your file name does not have a file type extension. `UIDocument` uses the file type extension to determine what type of file it is dealing with. I would try giving the file a type extension so "note.txt" or something. – Scott Thompson Nov 07 '21 at 02:33
  • @ScottThompson I just realized it says this B4Grad[70740:2938893] [Assert] failed to get type for URL (file:///Users/user/Library/Developer/CoreSimulator/Devices/83176304-1BB4-43C1-B37E-B3C1A3AAAF35/data/Containers/Data/Application/488D4A1D-91B6-446E-BF2C-8262FF3BB801/Documents/.asset/6C122A78-A817-4055-BBCD-06B6438528E5/note) error: The file “note” couldn’t be opened because there is no such file. – user2312844 Nov 07 '21 at 02:33
  • It says it failed to get type but at the end there is says there is no such a file - so which one is it? I'll try adding a type. – user2312844 Nov 07 '21 at 02:34
  • I don't want to change the file type because it would potentially mess with existing users' data. I checked the location of that directory pasted above and it is totally empty. I find that incredibly odd, because this exact code used to run fine.. How could I check if a file exists first before running this function? Looking for best practice here. – user2312844 Nov 07 '21 at 02:54
  • @ScottThompson `noteFile.save(to: noteFile.fileURL, for: .forCreating)` causes the same crash – user2312844 Nov 07 '21 at 02:58
  • @ScottThompson I tried adding a type like .txt and same error as well. I included NEFileManager in the OP as I believe this is where the root of the problem is. (Clearly there is an issue with file management and this class is responsible for determining directory paths) – user2312844 Nov 07 '21 at 04:13
  • Override the savedFileType getter to change file type – Ptit Xav Nov 07 '21 at 11:04
  • @PtitXav We found out this is an Apple Chip issue - we ran the code on an intel base Mac just fine. Incredibly bizarre behaviour. – user2312844 Nov 07 '21 at 17:00

0 Answers0