I thought those objects had been deprecated and when I got a "Use of unresolved identifier 'NSOpenPanel'" error I assumed they were no longer available. I imported Foundation and UIKit - are they in another module?
Thanks!
Post
Replies
Boosts
Views
Activity
Here is the whole of the code... (I get: Use of unresolved identifier 'NSOpenPanel'
import Foundation
import UIKit
class ViewController: UIViewController {
var fileManager = FileManager()
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func readButtonTapped(_ sender: Any) {
let dialog = NSOpenPanel()
}
}
I'm building a UIKit app that does remote data collection that needs to be able to create a binary file in the cloud for subsequent processing.
I've started working on a static library to package the file I/O logic and was going to use NSSavePanel in a test app to allow me to specify the URL where the file should be created.
I used NSSavePanel in an iOS app for a similar purpose about 4 years ago, so it *used* to be available. Hence "gone".
I'll have to do further research RE how to approach the problem. It's not really a "document centric" app, just needs to be able to generate the binary file as part of its normal workflow.
I actually fished back in some old files and found the prior work - I was developing a file I/O class for use in an iOS app, but I did the development work for the library in a Mac app. Embarrassed, but I'll repeat the trick since it's so much easier to deal with the file system in MacOS. I'm just getting back into an iOS project and am still feeling my way around in new versions of everything since I last coded.
Thanks Quinn for taking time to steer me straight!
Quinn - thanks for the links. The presentations were very informative, and I understand more about the risks, rewards and philosophy of using pointers in Swift (though I’m still not entirely clear on why my pointer is dangling nor exactly what to do about it.)
I didn’t set out to use ‘unsafe’ pointers - I set out to get data from a file into a local struct. I rooted around in the dev docs and turned up the FileHandle object; overview: “You use file handle objects to access data associated with files, sockets, pipes, and devices. For files, you can read, write, and seek within the file. “ Yeah, that’s the ticket. The .seek method gets me to where I need to be to get at the data I want and the .readData(ofLength: ) method reads a specified number of bytes of data from the file specified URL and loads them into a Data buffer for me. How do I get the data from the Data buffer into my local variables? The .copyBytes function seemed like a good candidate! How do I specify *where* to copy the data? With a UnsafeMutablePointer<UInt8>! And my mother always told me to “be safe”. (Interestingly enough, both the FileHandle “readFrom” and “seek” methods have been deprecated. (!?) I’ll worry about that later, I’m just hacking to see how I could satisfy a series of use cases at this point, and to get some hands-on experience with the tools and technology.)
I admit, I’m a rank novice with developing in Swift using Xcode. But I’m a seasoned developer (laid down my first lines of Fortran code in 1970) with experience on many platforms using a variety of languages, including a long stint with C coding close to the metal back in the early to mid-80’s. I started developing for Macintosh in the late 80’s (using a framework called Splash) and worked on a commercial product for Newton in the late 90’s - I even gave a presentation on that app at WWDC c.1998. I retired some years ago but have really missed development work, so decided I would take a crack at developing an iOS app. I first approached the tools about 4 years ago, but life intervened and that goal got delayed until now. I’ve been at it for a couple of months (my Covid pastime) but have found the learning curve daunting. Maybe this dog is too old to learn new tricks.
I’m not even sure if the FileHandle fork was the right road to take for file I/O. The dev docs are a rich source for reference, but aren’t necessarily the best resource to get an idea about how to approach a specific problem. I’ve always found (working & simple) sample code to be the best jump start, but have had little luck finding any useful examples for doing file I/O - maybe I picked a bad topic to start with. I think I’ll park this experiment and move on to other frameworks, for now.
If anyone has experience with doing file access with binary files and would be able to offer some insight as to how you approached the problem I would be most grateful to hear from you!
This mod works and the compiler isn't warning me of dangling pointers. Safer??
private func loadFileHeader( fileHandle: FileHandle, fileHeader: inout FileHeader ) {
withUnsafeMutableBytes( of: &fileHeader.fileCode ) { pointer in
let data = fileHandle.readData(ofLength: sizeOfInt32)
data.copyBytes(to: pointer)
}
fileHeader.fileCode = fileHeader.fileCode.bigEndian
SeekTo(fileHandle: fileHandle, offset: 24)
withUnsafeMutableBytes( of: &fileHeader.fileLength ) { pointer in
let data = fileHandle.readData(ofLength: sizeOfInt32)
data.copyBytes(to: pointer)
}
fileHeader.fileLength = fileHeader.fileLength.bigEndian
//etc.
Quinn - Thanks for the post link. I got more out of studying that thread for 20 minutes than I got out of untold hours scratching around in the dev docs. Seriously.
The file header struct is defined in Swift. BUT…
The header data in the file is composed of 9 UInt32 values followed by 8 Double values, or:
9 ea. UInt32 * 4 bytes/UInt32 + 8 ea. Double * 8 bytes/Double = 36 bytes + 64 bytes = 100 bytes
I verified that this data is indeed packed into exactly 100 bytes by inspecting the file with a hex editor.
The corresponding Swift struct dutifully defines 9 UInt32 values followed by 8 Doubles. I printed out the MemoryLayout of the struct and got:	size = 104
	align = 8
	stride = 104
The 104 makes perfect sense since the alignment is 8 (due to the doubles), requiring a padding of 4 bytes after the 9 (odd) UInt32 values to force the 8 byte alignment for the doubles. This is, I think, the reason for your caution RE the layout of the struct - I can’t just blast 100 bytes of disk data into address of the 104 byte struct or the doubles would all be misaligned. BUT - in the last snippet I sent I’m setting the pointer to the address of the target struct property, not the base address of the struct, then I copy the correct number of bytes for the target data type (i.e. 4 or 8) from the Data buffer to the struct property (then clean up the byte ordering, as needed, for correct endian order). This seems to address your concern about memory mapping of the struct v. the packed bytes in the disk image. Safe or Not Safe?
The other big takeaway from your post is I shouldn’t nickel & dime the disk input an int or double at a time, but should pull in large logical blocks at once (e.g. the whole file header in a single read) and then copy the bytes into the target structs one property at a time. The test data file I’m using is ~1.5 Mb so I’d tend to avoid loading the whole file up front. (But then I’m from the generation where memory was so preciou$ we skimped on carrying the ’19’ in the year ‘1967’, thus setting up the eventual Y2K panic! Who knew we'd still be around when the bill came due on that decision!)
I’ll also keep in mind that FileHandle.seek(toOffset:) and FileHandle.readData(ofLength:) are, among others, going away some time in the future when I get down to actually implementing this logic for real.
Thank you again for your help. You’ve got me going again so I can focus on the next hurdle.
Peace!
OK - in this flavor I avoid any byte moves into the fileHeader struct at all by using a local variable in the getUInt32 method, and then use the returned variable to assign to the struct property. (I also tell the getUInt method which endian the value from the file is in and have it do the byte swaps if it's not the same as the current host's preferred endian order.)
Safe yet?
				 private func loadFileHeader(fileHandle: FileHandle, fileHeader: inout fileHeader) {
						 let data = fileHandle.readData(ofLength: FILE_HEADER_SIZE)
						 if data.count == FILE_HEADER_SIZE {
						
											fileHeader.fileCode = getUInt32( from: data, at: 0, endian: Endian.big )
											fileHeader.fileLength = getUInt32( from: data, at: 24, endian: Endian.big )
											fileHeader.version = getUInt32( from: data, at:28, endian: Endian.little )
						 /* etc. */
							 } else {
		 /* not a valid file err */
							}
		 }
				 private func getUInt32(from: Data, at: Int, endian: Endian) -> UInt32 {
						 var unsignedInt32: UInt32 = 0
						 _ = withUnsafeMutableBytes( of: &unsignedInt32 )	{ pointer in
								 from.copyBytes(to: pointer, from: at..<at+4)
								if endian != getHostEndian() {
										 swapBytes(a: &pointer[0], b: &pointer[3])
										 swapBytes(a: &pointer[1], b: &pointer[2])
								 }
						 }
						 return unsignedInt32
				 }
				 private func swapBytes(a: inout UInt8, b: inout UInt8) {
						 let t = a
						 a = b
						 b = t
				 }
				 public enum Endian {
						 case big
						 case little
				 }
				 private func getHostEndian() -> Endian {
						 var value: UInt = 0x76543210
						 var endian: Endian = Endian.big
						 _ = withUnsafeBytes(of: &value) { pointer in
								 if pointer[0] == 0x10 {
										 endian = Endian.little
								 }
						 }
						 print("Endian on this machine is \(endian)")
						 return endian
				 }
Many thanks for all your help Quinn. I learned a lot struggling through this exercise. (Maybe this old dog can learn a few new tricks!)