How to extract local zip files using Swift with Zip library (SwiftUI) - swift

I'd like to download an archive file (zip extension) and extract it. This app downloads zip successfully, but it won't decompress zip file.
Zip framework library: https://github.com/marmelroy/Zip
Disabled sandbox (Xcode)
Allowed arbitrary loads (HTTP)
Password of archive file is: gameInfos.gameBinaryName
Location of archive file would be:
/Users/Shared/Bitmap Production/Games/\(gameInfos.gameBinaryName)/\(gameInfos.gameBinaryName).zip
Destination folder would be like:
/Users/Shared/Bitmap Production/Games/\(gameInfos.gameBinaryName)
Using let variable zipPath, zipPathURL, zipPathURL1, zipPathURL2 didn't work and I dunno why.
The console says:
...
Downloading: 1.0
OX is downloaded to your computer.
Decompressing of OX failed. Check /Users/Shared/Bitmap Production/Games/OX/OX.zip and decompress it manually. Error code: unzipFail
This code is summarized. Full code is here: https://github.com/bmplatina/BitmapMac
import SwiftUI
import Foundation
import Alamofire
import Zip
let bitmapHomeFolder = "/Users/Shared/Bitmap Production"
let bitmapGameFolder = bitmapHomeFolder + "/Games"
struct gameInfo: Codable { // This is from server's api
// ...
var gameDownloadMacURL: String = "" // Download Link for macOS Platform
var gameTitle: String = "OX" // "OX" is for example
var gameBinaryName: String = "OX" // "OX" is for example
}
struct gameButtons: View {
var gameInfos: gameInfo
var body: some View {
Button(action: { downloadGame() }) {
Text("Download")
}
func downloadGame() {
let fileManager = FileManager.default
let appURL = fileManager.urls(for: .userDirectory, in: .localDomainMask)[0]
let fileURL = appURL.appendingPathComponent("Shared/Bitmap Production/Games/\(gameInfos.gameBinaryName)/\(gameInfos.gameBinaryName).zip")
let destination: DownloadRequest.Destination = { _, _ in
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
AF.download(gameInfos.gameDownloadMacURL, method: .get, parameters: nil, encoding: JSONEncoding.default, to: destination).downloadProgress { (progress) in
print("Downloading: \(progress.fractionCompleted)")
}
.response { response in
if response.error != nil {
print("Download Failed. Check your internet connection.")
}
else {
print("\(gameInfos.gameTitle) is downloaded to your computer.")
unzipGame(zipPathURL: fileURL)
}
}
}
func unzipGame(zipPathURL: URL) {
do {
let fileManager = FileManager.default
let gameDirectory = fileManager.urls(for: .userDirectory, in: .localDomainMask)[0]
let zipPath = gameDirectory.appendingPathComponent("Shared/Bitmap Production/Games/\(gameInfos.gameBinaryName)/\(gameInfos.gameBinaryName).zip")
// let zipPathURL1 = URL(fileURLWithPath: bitmapGameFolder + "/\(gameInfos.gameBinaryName)/\(gameInfos.gameBinaryName).zip")
let zipPathURL2 = URL(string: "file:///Users/Shared/Bitmap%20Production/Games/\(gameInfos.gameBinaryName)/\(gameInfos.gameBinaryName).zip")!
let destinationDirectory = gameDirectory.appendingPathComponent("Shared/Bitmap Production/Games/\(gameInfos.gameBinaryName)/")
try Zip.unzipFile(zipPathURL2, destination: destinationDirectory, overwrite: true, password: gameInfos.gameBinaryName, progress: { (progress) -> () in
print("Unzipping: \(progress)")
}) // Unzip
}
catch {
print("Decompressing of \(gameInfos.gameTitle) failed. Check \(bitmapGameFolder)/\(gameInfos.gameBinaryName)/\(gameInfos.gameBinaryName).zip and decompress it manually. Error code: \(error)")
}
}
}

Related

File Exporter shows export sheet with no files

I'm trying to write JSON Data to a file, then export it.
Writing went fine:
let jsonData = try JSONEncoder().encode(JSON)
if let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let pathWithFileName = documentDirectory.appendingPathComponent("json.json")
filePath = pathWithFileName.path()
try jsonData.write(to: pathWithFileName)
exportFile = true
}
I also have a Doc struct which is necessary for .fileExporter()
struct Doc: FileDocument {
var url: String
static var readableContentTypes: [UTType] {[.json]}
init(url: String) {
self.url = url
}
init(configuration: ReadConfiguration) throws {
self.url = ""
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let file = try! FileWrapper(url: URL(fileURLWithPath: url), options: .immediate)
return file
}
}
When I print out the file's contents from the function above, everything shows up.
However when I call .fileExporter():
.fileExporter(isPresented: $exportFile, document: Doc(url: filePath), contentType: .json) { (result) in
do {
let fileURL = try result.get()
print(fileURL)
} catch {
print(error.localizedDescription)
}
}
The export sheet shows up, but there are no items to be moved (see picture)
I have no idea what's going on here so good luck to everyone who tries to solve this.

View pdf documents

I have a table with the names of pdf documents. Previously, there were 3 documents and each one has its own ViewController. How can I make it so that with hundreds of documents, I would select one from the table and show it on the View, if I select another document, then on the same View show another document.
while I have such a function, where I substituted the name of the documents in each class and showed it in different representations. But now I need to display everything on one ViewController when selecting any document
import UIKit
import PDFKit
class pdfViewClass {
class func filePDfFunc(nameFile: String, formatFile:String,
nameView:PDFView)
{
if let path = Bundle.main.path(forResource: nameFile,
ofType:formatFile) {
if let pdfDocument = PDFDocument(url: URL(fileURLWithPath:
path)) {
nameView.autoScales = true
nameView.displayDirection = .vertical
nameView.document = pdfDocument
}
}
}
}
You can use Native Apple UIDocumentInteractionController for viewing PDF file.
Create a function like below for View PDF
func viewPdf(urlPath: String, screenTitle: String) {
// open pdf for booking id
guard let url = urlPath.toUrl else {
print("Please pass valid url")
return
}
self.downloadPdf(fileURL: url, screenTitle: screenTitle) { localPdf in
if let url = localPdf {
DispatchQueue.main.sync {
self.openDocument(atURL: url, screenTitle: screenTitle)
}
}
}
}
Function for download PDF
// method for download pdf file
func downloadPdf(fileURL: URL, screenTitle: String, complition: #escaping ((URL?) -> Void)) {
// Create destination URL
if let documentsUrl: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let destinationFileUrl = documentsUrl.appendingPathComponent("\(screenTitle).pdf")
if FileManager.default.fileExists(atPath: destinationFileUrl.path) {
try? FileManager.default.removeItem(at: destinationFileUrl)
}
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url: fileURL)
let task = session.downloadTask(with: request) { tempLocalUrl, response, error in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Successfully downloaded. Status code: \(statusCode)")
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl)
complition(destinationFileUrl)
} catch let writeError {
print("Error creating a file \(destinationFileUrl) : \(writeError)")
}
} else {
print("Error took place while downloading a file. Error description: \(error?.localizedDescription ?? "N/A")")
}
}
task.resume()
} else {
complition(nil)
}
}
Function for open documents
func openDocument(atURL url: URL, screenTitle: String) {
self.documentInteractionController.url = url
self.documentInteractionController.name = screenTitle
self.documentInteractionController.delegate = self
self.documentInteractionController.presentPreview(animated: true)
}
On tap of tableView pass the specific index URL
viewPdf(urlPath: "http://www.africau.edu/images/default/sample.pdf", screenTitle: "Tesing Document")
You can do it using WKWebView easily. Use WKWebView to load your pdf doc.

Could not open obj file

so I'm working on a project that pulls zip files which include .obj files, unzips them and displays them using ARkit. At the moment I've been able to unzip and save the file in my documents directory as a .obj file and it says the file exists, but when I try to convert it into a scn it says "Could not open OBJ file". Here is my code and I'm trying to figure out where I went wrong. The file size is 314 KB so I know that's not the issue and the obj file isn't corrupted because I can download it to my computer and open it up.
class ViewController: UIViewController {
var testData: Store?
var logoImage: UIImage?
var urlForObject: String?
var tempDirectoryString: String?
var tempDirectoryURL: URL?
var testModelData: Data?
var testData2: Data?
var testData3: Models?
var tempDocumentsURL: URL?
#IBOutlet weak var logoImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.clearCowZipFolder()
self.clearCowFolder()
self.requestZipFile(){ response in
self.testData2 = response
do{
let json = try? JSONSerialization.jsonObject(with: response, options: [])
if let array = json as? [String] {
if let firstObject = array.first {
//print(firstObject)
}
for object in array {
// access all objects in array
}
for case let string as String in array {
// access only string values in array
}
}
}catch{
print("errorrrr")
}
}
self.tempDocumentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL!
let destinationFileUrl = self.tempDocumentsURL!.appendingPathComponent("Man.zip")
var destPath = self.tempDocumentsURL!.appendingPathComponent("Man")
//Create URL to the source file you want to download
let fileURL = URL(string: "http://markitapi.com/stores/TestStore/models/Man")
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileURL!)
let task = session.downloadTask(with: request) { (tempLocalUrl, response3, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response3 as? HTTPURLResponse)?.statusCode {
print("Successfully downloaded. Status code: \(statusCode)")
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl)
} catch (let writeError) {
print("Error creating a file \(destinationFileUrl) : \(writeError)")
}
do{
let unzipDirectory = try Zip.quickUnzipFile(destinationFileUrl) //Unzip
print("HHERHERHEHRE")
destPath = unzipDirectory
}catch{
print("error while unzipping")
}
print("UNZIPPED PATH")
//print(destPath)
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
if let pathComponent = url.appendingPathComponent("Man/Man.obj") {
let filePath = pathComponent.path
//print(filePath)
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
print("FILE AVAILABLE")
} else {
print("FILE NOT AVAILABLE")
}
} else {
print("FILE PATH NOT AVAILABLE")
}
let url2 = (String(describing: destPath) + "Man.obj")
let url3 = URL(fileURLWithPath: url2)
print(url3.pathExtension)
let asset = MDLAsset(url: url3)
//print(asset)
let object = asset.object(at: 0)
let node = SCNNode(mdlObject: object)
//print(node)
}
} else {
//print("Error took place while downloading a file. Error description: %#", error?.localizedDescription);
}
}
task.resume()
}
func clearCowZipFolder() {
let fileNameToDelete = "Man.zip"
var filePath = ""
// Fine documents directory on device
let dirs : [String] = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.allDomainsMask, true)
if dirs.count > 0 {
let dir = dirs[0] //documents directory
filePath = dir.appendingFormat("/" + fileNameToDelete)
//print("Local path = \(filePath)")
} else {
print("Could not find local directory to store file")
return
}
do {
let fileManager = FileManager.default
// Check if file exists
if fileManager.fileExists(atPath: filePath) {
// Delete file
try fileManager.removeItem(atPath: filePath)
} else {
print("File does not exist")
}
}
catch let error as NSError {
print("An error took place: \(error)")
}
}
func clearCowFolder() {
let fileNameToDelete = "Man"
var filePath = ""
// Fine documents directory on device
let dirs : [String] = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.allDomainsMask, true)
if dirs.count > 0 {
let dir = dirs[0] //documents directory
filePath = dir.appendingFormat("/" + fileNameToDelete)
//print("Local path = \(filePath)")
} else {
print("Could not find local directory to store file")
return
}
do {
let fileManager = FileManager.default
// Check if file exists
if fileManager.fileExists(atPath: filePath) {
// Delete file
try fileManager.removeItem(atPath: filePath)
} else {
print("File does not exist")
}
}
catch let error as NSError {
print("An error took place: \(error)")
}
}
func requestZipFile(success successBlock: #escaping (Data) -> Void){
Alamofire.request("http://markitapi.com/stores/TestStore/models").responseJSON { (response) in
do{
if(response.result.isSuccess){
successBlock(response.data!)
}
}catch{
print("error")
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The first part of the code checks to see if the file is available and it does print that statement, so I know the file is there.

Is it possible to load images recovered from a server in runtime into a plane object?

I have been asked to build an app that shows a catalog with AR, so what I need to do is pretty simple: when an user chooses a product I must load the image recovered in base64 from the server into a plane object. Is this possible with swift - arkit ? Or are all the sprites/images/textures required to be previously loaded into the assets folder?
You can definitely download resources from a server, save them to the device (e.g in NSDocumentsDirectory), and then load with the file URL. I do it for a similar use case as yours -at least it sounds so, per the description you gave-
EDIT
Here's the relevant code. I use Alamofire to download from the server and ZIPFoundation for unzipping. I believe that if you just need to download an image, it'll be a bit simpler, probably not needing the unzip part.
let modelsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
func loadNodeWithID(_ id: String, completion: #escaping (SCNNode?) -> Void) {
// Check that assets for that model are not already downloaded
let fileManager = FileManager.default
let dirForModel = modelsDirectory.appendingPathComponent(id)
let dirExists = fileManager.fileExists(atPath: dirForModel.path)
if dirExists {
completion(loadNodeWithIdFromDisk(id))
} else {
let dumbURL = "http://yourserver/yourfile.zip"
downloadZip(from: dumbURL, at: id) {
if let url = $0 {
print("Downloaded and unzipped at: \(url.absoluteString)")
completion(self.loadNodeWithIdFromDisk(id))
} else {
print("Something went wrong!")
completion(nil)
}
}
}
}
func loadNodeWithIdFromDisk(_ id: String) -> SCNNode? {
let fileManager = FileManager.default
let dirForModel = modelsDirectory.appendingPathComponent(id)
do {
let files = try fileManager.contentsOfDirectory(atPath: dirForModel.path)
if let objFile = files.first(where: { $0.hasSuffix(".obj") }) {
let objScene = try? SCNScene(url: dirForModel.appendingPathComponent(objFile), options: nil)
let objNode = objScene?.rootNode.firstChild()
return objNode
} else {
print("No obj file in directory: \(dirForModel.path)")
return nil
}
} catch {
print("Could not enumarate files or load scene: \(error)")
return nil
}
}
func downloadZip(from urlString: String, at destFileName: String, completion: ((URL?) -> Void)?) {
print("Downloading \(urlString)")
let fullDestName = destFileName + ".zip"
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let fileURL = modelsDirectory.appendingPathComponent(fullDestName)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(urlString, to: destination).response { response in
let error = response.error
if error == nil {
if let filePath = response.destinationURL?.path {
let nStr = NSString(string: filePath)
let id = NSString(string: nStr.lastPathComponent).deletingPathExtension
print(response)
print("file downloaded at: \(filePath)")
let fileManager = FileManager()
let sourceURL = URL(fileURLWithPath: filePath)
var destinationURL = modelsDirectory
destinationURL.appendPathComponent(id)
do {
try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil)
try fileManager.unzipItem(at: sourceURL, to: destinationURL)
completion?(destinationURL)
} catch {
completion?(nil)
print("Extraction of ZIP archive failed with error: \(error)")
}
} else {
completion?(nil)
print("File path not found")
}
} else {
// Handle error
completion?(nil)
}
}
}

Download file from server using Swift

Hi I have a whole bunch of .mp3 files I want to use with NSFileManager and store in the documents folder. Is there a way I can download the .mp3 files online and then have it save to the documents folder? This is what I'm using for a local file.
let filemanager = NSFileManager.defaultManager()
let documentsPath : AnyObject = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask,true)[0]
let destinationPath:NSString = documentsPath.stringByAppendingString("/Attention.mp3")
if (!filemanager.fileExistsAtPath(destinationPath)) {
var theError: NSError?
let fileForCopy = NSBundle.mainBundle().pathForResource("Attention",ofType:"mp3")
filemanager.copyItemAtPath(fileForCopy!,toPath:destinationPath, error: &theError)
if (theError == nil) {
println("The music files has been saved.")
} else {
println("Error")
}
} else {
println("The files already exist")
}
edit/update: Xcode 11.5 • Swift 5.2
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player: AVPlayer!
override func viewDidLoad() {
super.viewDidLoad()
let alarm = URL(string: "https://www.ringtonemobi.com/storage/upload/user_id_1/iphone-5-alarm-2016-08-21-01-49-25.mp3")!
do {
try alarm.download(to: .documentDirectory) { url, error in
guard let url = url else { return }
self.player = AVPlayer(url: url)
self.player.play()
}
} catch {
print(error)
}
}
}
import Foundation
extension URL {
func download(to directory: FileManager.SearchPathDirectory, using fileName: String? = nil, overwrite: Bool = false, completion: #escaping (URL?, Error?) -> Void) throws {
let directory = try FileManager.default.url(for: directory, in: .userDomainMask, appropriateFor: nil, create: true)
let destination: URL
if let fileName = fileName {
destination = directory
.appendingPathComponent(fileName)
.appendingPathExtension(self.pathExtension)
} else {
destination = directory
.appendingPathComponent(lastPathComponent)
}
if !overwrite, FileManager.default.fileExists(atPath: destination.path) {
completion(destination, nil)
return
}
URLSession.shared.downloadTask(with: self) { location, _, error in
guard let location = location else {
completion(nil, error)
return
}
do {
if overwrite, FileManager.default.fileExists(atPath: destination.path) {
try FileManager.default.removeItem(at: destination)
}
try FileManager.default.moveItem(at: location, to: destination)
completion(destination, nil)
} catch {
print(error)
}
}.resume()
}
}
Original answer
Xcode 8.3.2 • Swift 3.1
if let audioUrl = URL(string: "http://freetone.org/ring/stan/iPhone_5-Alarm.mp3") {
// create your document folder url
let documentsUrl = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
// your destination file url
let destination = documentsUrl.appendingPathComponent(audioUrl.lastPathComponent)
print(destination)
// check if it exists before downloading it
if FileManager.default.fileExists(atPath: destination.path) {
print("The file already exists at path")
} else {
// if the file doesn't exist
// just download the data from your url
URLSession.shared.downloadTask(with: audioUrl, completionHandler: { (location, response, error) in
// after downloading your data you need to save it to your destination url
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("audio"),
let location = location, error == nil
else { return }
do {
try FileManager.default.moveItem(at: location, to: destination)
print("file saved")
} catch {
print(error)
}
}).resume()
}
}
Xcode 10.1, Swift 4
I used the example above from #leo-dabus but broke up the code a bit into two functions. One flaw I found in that approach was that it did not handle the case where the file is already downloaded.
This example will remove any previous file that was already downloaded and write the latest version.
/// Downloads a file asynchronously
func loadFileAsync(url: URL, completion: #escaping (Bool) -> Void) {
// create your document folder url
let documentsUrl = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
// your destination file url
let destination = documentsUrl.appendingPathComponent(url.lastPathComponent)
log.info(m: "downloading file from URL: \(url.absoluteString)")
if FileManager().fileExists(atPath: destination.path) {
print("The file already exists at path, deleting and replacing with latest")
if FileManager().isDeletableFile(atPath: destination.path){
do{
try FileManager().removeItem(at: destination)
print("previous file deleted")
self.saveFile(url: url, destination: destination) { (complete) in
if complete{
completion(true)
}else{
completion(false)
}
}
}catch{
print("current file could not be deleted")
}
}
// download the data from your url
}else{
self.saveFile(url: url, destination: destination) { (complete) in
if complete{
completion(true)
}else{
completion(false)
}
}
}
}
func saveFile(url: URL, destination: URL, completion: #escaping (Bool) -> Void){
URLSession.shared.downloadTask(with: url, completionHandler: { (location, response, error) in
// after downloading your data you need to save it to your destination url
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let location = location, error == nil
else { print("error with the url response"); completion(false); return}
do {
try FileManager.default.moveItem(at: location, to: destination)
print("new file saved")
completion(true)
} catch {
print("file could not be saved: \(error)")
completion(false)
}
}).resume()
}
I found the #leo-dabus worked straight away, but had to make two minor changes for my needs. This might be helpful for others.
Change #1: Handle filenames that come included with a path-extension
if let fileName = fileName {
if fileName.hasSuffix(self.pathExtension) {
destination = directory
.appendingPathComponent(fileName)
} else {
destination = directory
.appendingPathComponent(fileName)
.appendingPathExtension(self.pathExtension)
}
} else {
destination = directory
.appendingPathComponent(lastPathComponent)
}
Change #2: If the destination file exists, generate a unique name
E.g. generate File (2).txt to avoid overwriting File.txt, like a web browser would.
if !overwrite {
let pathExtension = destination.pathExtension
let lastComponent = destination.deletingPathExtension().lastPathComponent
var copyNumber = 2
var attemptedURL = destination
while FileManager.default.fileExists(atPath: attemptedURL.path) {
attemptedURL = destination
.deletingPathExtension()
.deletingLastPathComponent()
.appendingPathComponent("\(lastComponent) (\(copyNumber))")
.appendingPathExtension(pathExtension)
copyNumber += 1
}
destination = attemptedURL
}