Check zip file containing video content or not in swift - ios

I have to preview zip files using Document Interaction Controller, but zip files containing video content should not be previewed. Is there a way to check zip file containing video content using swift?

There's a 3rd party library called ZIPFoundation that makes it convenient to traverse zip entries.
Install pod 'ZipFoundation' in your project.
Copy / paste below helper code in your project.
import Foundation
import ZIPFoundation
extension String {
var pathExtension: String {
URL(fileURLWithPath: self).pathExtension
}
}
extension Archive {
var containsVideo: Bool {
let videoTypes: [String] = ["MOV", "MP4", "AVI"]
for entry in self where entry.type == .file {
let type = entry.path.pathExtension.uppercased()
if videoTypes.contains(type) {
return true
}
}
return false
}
}
From the call site, you can use it like following -
if let zipURL = Bundle.main.url(forResource: "Test", withExtension: "zip"),
let arhive = Archive(url: zipURL, accessMode: .read) {
print(arhive.containsVideo)
}

Related

How could one produce an icon from a document (based on file type), selected via UIDocumentationPickerController?

I am new fairly new to swift development but am obsessed and in love with learning, I have not only dedicated myself to learning but I am starting to apply my knowledge and have built a small messaging app with the capability to select a file. I have successfully created the ability for future users of my app to send documents via the messaging system, and I am able to show the file name, type etc. However, I am struggling to understand (reading apple documentation and other community developer's posts regarding this) how to fetch a specific icon type from the iOS system based on the file's extension type (e.g. xlsx), let alone the UTIType (e.g. public.movie -> mp4, etc.).
If anyone has suggestions on how/where to facilitate this, I would like to set my current, generic file icon to a custom filetype, as the one in the attached image example below (PDF in example):
Tarun's answer was quick and very helpful in getting one step further, however, it returned this icon for pdf, png, and jpeg, all tested (regardless of the aforementioned differentiation in file type.
Here is my code:
guard let fileUrl = message.documentUrl else { return }
let fileNameUrl = (fileUrl as NSString).lastPathComponent
let fileName = fileNameUrl.components(separatedBy: "?")[0].removingPercentEncoding
let documentInteractionController = UIDocumentInteractionController()
documentInteractionController.name = fileName
documentInteractionController.url = URL(fileURLWithPath: fileNameUrl)
let fileTypeIcon = documentInteractionController.icons.first
fileIcon.image = fileTypeIcon
When you get a file url from UIDocumentPickerViewController, you need to query it's uti / mimeType etc. which you can do from following helpers.
import Foundation
import MobileCoreServices
public extension URL {
private var utiCFString: CFString? {
UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension,
self.pathExtension as NSString,
nil
)?.takeRetainedValue()
}
var uti: String? {
self.utiCFString as String?
}
var mimeType: String? {
if let utiCFString = self.utiCFString {
return UTTypeCopyPreferredTagWithClass(
utiCFString,
kUTTagClassMIMEType
)?.takeRetainedValue() as String?
}
return nil
}
}
extension ViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let url = urls.first else { return }
guard let mimeType = url.mimeType else {
print("Invalid file type")
return
}
let uti = url.uti
}
}
Once you have all the info you need, you can try this -
let documentInteractionController = UIDocumentInteractionController()
documentInteractionController.name = filename
documentInteractionController.url = URL(fileURLWithPath: filePath)
documentInteractionController.uti = uti
let mayBeIcon = documentInteractionController.icons.last
You can use QuickLook Thumbnailing framework.Have a look at the Apple documentation.
https://developer.apple.com/documentation/quicklookthumbnailing

Localization request permission base on the application language not device language

Localization is working fine in my application. I want localization the permission dialog text messages. It is working fine with the device language changes but I want to change message according to my application language.
I have tried the following code
import UIKit
class LocalizeHelper: NSObject {
private var myBundle: Bundle? = nil
static let shared: LocalizeHelper = {
let instance = LocalizeHelper()
return instance
}()
override init() {
super.init()
// use systems main bundle as default bundle
myBundle = Bundle.main
}
func localizedString(forKey key: String) -> String {
return myBundle!.localizedString(forKey: key, value: "", table: nil)
}
// Converted with Swiftify v1.0.6331 - https://objectivec2swift.com/
func setLanguage(_ lang: String) {
// path to this languages bundle
let path: String? = Bundle.main.path(forResource: lang, ofType: "lproj")
if path == nil {
// there is no bundle for that language
// use main bundle instead
myBundle = Bundle.main
}
else {
// use this bundle as my bundle from now on:
myBundle = Bundle(path: path!)
// to be absolutely shure (this is probably unnecessary):
if myBundle == nil {
myBundle = Bundle.main
}
}
}
func getLanguage() -> String {
print("\(String(describing: myBundle?.bundlePath.last))")
//return myBundle!.bundlePath.last >> Error
return myBundle!.bundlePath.lastCharacter!
}
}
extension String {
public var lastCharacter: String? {
guard let aLast = self.last else {
return nil
}
return String(aLast)
}
}
I have surfed in StackOverflow but didn't find any solution. Any help shell we appreciated.
Your best bet is to save the application's language in the user defaults as a string for example and then when you take it out of user defaults parse it as a custom app language enum. If you don't have any app language save you can always fall through to the device language but prefer or override it with the app language in user defaults not being nil. Also preferably you should wrap your user defaults interactions with a user defaults manager that you access in your view controller.
So the suggested steps are:
create userdefualts manager
define language enum
implement setLanguage func language parameter
implement getLanguage func language output with device language or
english as guard return

MessageAppExtension: how to load sticker images from assets to MSStickerBrowserView?

Alright, I know this is new for everybody but I would think it'd be a simple concept - I am following this here to make a custom sticker message app extension:
https://code.tutsplus.com/tutorials/create-an-imessage-app-in-ios-10--cms-26870
Ive copied everything exactly and am trying to create a basic MSStickerBrowserView displaying (then later filtering using logic, but haven't attempted that yet) sticker pngs I have in my assets folder here:
The tutorial did not load from assets it seems but rather just from their project, regardless their code is old as here:
var stickers = [MSSticker]()
func loadStickers() {
for i in 1...2 {
if let url = Bundle.main.urlForResource("Sticker \(i)", withExtension: "png") { //ERROR!!
do {
let sticker = try MSSticker(contentsOfFileURL: url, localizedDescription: "")
stickers.append(sticker)
} catch {
print(error)
}
}
}
}
I get the error
Bundle has no member URLforResource
I can't find anything on this. How can I just display my stickers programmatically in the app?
Error:
These are the images Im trying to load regardless of their name:
The reason that tutorial doesn't use asset catalogs is that you cannot get a valid fileURL for images placed in an .xcassets folder when calling the urlForResource method on the bundle.
You need to add your assets individually like you would other files you are bringing in to the app. Calling pathForResource or urlForResource on the bundle at that point will no longer return nil.
EDIT: Here is a function that will take a folder name, loop through it's contents and return [MSSticker]? based on what it finds
func createStickers(from folderName: String) -> [MSSticker]? {
guard
let path = Bundle.main.resourcePath
else { return nil }
var stickers = [MSSticker]()
let folderPath = "\(path)/\(folderName)"
let folderURL = URL(fileURLWithPath: folderPath)
//get a list of urls in the chosen directory
do {
let imageURLs = try FileManager.default.contentsOfDirectory(at: folderURL,
includingPropertiesForKeys: nil,
options: .skipsHiddenFiles)
//loop through the found urls
for url in imageURLs {
//create the sticker and add it, or handle error
do {
let sticker = try MSSticker(contentsOfFileURL: url, localizedDescription: "yourDescription")
stickers.append(sticker)
} catch let error {
print(error.localizedDescription)
}
}
} catch let error {
print(error.localizedDescription)
}
//return nil if stickers array is empty
return stickers.isEmpty ? nil : stickers
}
This should let you just call this and get what you are after:
let stickers = createStickers(from: "YourFolderName")
Please note not to include the forward slash ('/') at the beginning of the folder name.
Just replace "resourceUrl" with:
let url = Bundle.main.url(forResource: "Sticker \(i)", withExtension: "png")
The code got replaced in Swift 3.
You can put the images in a folder like so (XCODE Viewport):
It make things more organised but doesnt need as much code as if you would put them in a .xcasset.
It can be put done by creating a new group instead of creating an .xcasset by (Right Clicking Message Extension and clicking New Group):
The following code for the StickerBrowserView can be called like so:
import UIKit
import Messages
class StickerBrowserViewController: MSStickerBrowserViewController {
var stickers = [MSSticker]()
func changeBrowserViewBackgroundColor(color: UIColor){
stickerBrowserView.backgroundColor = color
}
func loadStickers(){
createSticker(asset: "1", localizedDescription:"grinning face")
createSticker(asset: "2", localizedDescription:"grimacing face")
createSticker(asset: "3", localizedDescription:"grinning face with smiling eyes")
createSticker(asset: "4", localizedDescription:"face with tears of joy")
createSticker(asset: "5", localizedDescription:"smiling face with open mouth")
createSticker(asset: "6", localizedDescription:"smiling face with open mouth and smiling eyes")
}
func createSticker(asset: String, localizedDescription: String){
guard let stickerPath = Bundle.main.path(forResource:asset, ofType:"png") else {
print("couldn't create the sticker path for", asset)
return
}
// we use URL so, it's possible to use image from network
let stickerURL = URL(fileURLWithPath:stickerPath)
let sticker: MSSticker
do {
try sticker = MSSticker(contentsOfFileURL: stickerURL, localizedDescription: localizedDescription)
// localizedDescription for accessibility
stickers.append(sticker)
}catch {
print(error)
return
}
}
override func numberOfStickers(in stickerBrowserView: MSStickerBrowserView) -> Int{
return stickers.count
}
override func stickerBrowserView(_ stickerBrowserView: MSStickerBrowserView, stickerAt index: Int) -> MSSticker{
return stickers[index] as MSSticker
}
}
(Ps. Not my blog, but found it on google and it has been very useful)

Why is my relation-object reinitialized each time it is accessed?

I use Realm Swift 0.95.3. In my project I'm working with a contact (not with contacts AddressBook), which contain information about the name, last name, middle name, email, … and photograph.
I have created a subclass of Object called Contact. I understand that the image, or any other binary data, should not be stored in the Realm. Especially as my images could be large enough ~5 Mb.
In any case, I wanted to store the image in the file system, and Realm stores only a link to this image. I thought that in the future I may need to attach other files (audio, PDF, …) to subclasses of Object. So I decided to create a separate class File, which is inherited from Object. The whole structure looks like.
class File: Object {
dynamic var objectID = NSUUID().UUIDString
private var filePath: String {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentsDirectory = paths[0] as NSString
return documentsDirectory.stringByAppendingPathComponent(objectID)
}
lazy var fileData: NSData? = {
return self.readFile()
}()
override static func primaryKey() -> String? {
return "objectID"
}
override static func ignoredProperties() -> [String] {
return ["fileData"]
}
override func copy() -> AnyObject {
let result = File(value: self)
result.objectID = NSUUID().UUIDString
result.fileData = fileData?.copy() as? NSData
return result
}
}
extension File {
//MARK:-
private func readFile() -> NSData? {
let lFilePath = filePath
print("Try to read file \(lFilePath)")
return NSData(contentsOfFile: lFilePath)
}
private func writeFileWithFileData() {
if let lFileData = fileData {
lFileData.writeToFile(filePath, atomically: true)
print("File \(filePath) was created")
} else {
removeFile()
}
}
private func removeFile() {
do {
try NSFileManager.defaultManager().removeItemAtPath(filePath)
print("File \(filePath) was removed")
} catch {
print(error)
}
}
}
As you can see, when trying to get the property fileData, data is received from the file system. Moreover fileData is declared with the keyword lazy, that is, I wish that the data is requested from disk to cache in this property. If this property is changed, the File object before saving to the database, I call writeFileWithFileData (), and data on the disk is overwritten. This system works as I need to, I to experiment. Then I created a Contact.
class Contact: Object {
dynamic var objectID = NSUUID().UUIDString
dynamic var firstName = ""
dynamic var lastName = ""
...
private dynamic var avatarFile: File?
var avatar: UIImage? {
get {
guard let imageData = avatarFile?.fileData else { return nil }
return UIImage(data: imageData)
}
set {
avatarFile = File()
guard let imageData = newValue else {
avatarFile?.fileData = nil
return
}
avatarFile?.fileData = UIImagePNGRepresentation(imageData)
}
}
override static func primaryKey() -> String? {
return "objectID"
}
override static func ignoredProperties() -> [String] {
return ["image"]
}
}
The problem is that when I choose a contact from the database, and am trying to get the avatar, then access to the file system occurs every time you access this property. That is, property fileData does not operate as lazy - as I thought at first. But then I looked at the memory address properties avatarFile, each time it is received, the address has changed. From this I can conclude that the object avatarFile is constantly requested from the database again, with any reference to this property. As a consequence, all its ignoredProperties are reset.
Why is the relation-object reinitialized each time it is accessed?
Share your opinion about my arch
It should be fine. Realm Objects are live links to their parent Realm object, not static copies, so their addresses do periodically change. This is normal, and the objects aren't getting re-allocated so you shouldn't see any memory issues here. As far as I'm aware, NSData itself is lazy, so the data won't actually be read until you explicitly request it.
Your architecture looks good! You're right in that it's usually not the best form to store file data directly inside a Realm file, but you've done a good job at making the Realm Object manage the external file data as if it was. :)

How to hide API keys in GitHub for iOS (SWIFT) projects?

Hello I'm trying to publish a iOS (SWIFT) personal project in GitHub but I'm afraid of sharing my private API keys and secrets with everybody.
I'm using parse so I have in my AppDelegate something like this:
let applicationId = "mySecretApplicationId"
let clientKey = "mySecretClientKey"
Parse.setApplicationId(applicationId!, clientKey: clientKey!)
I would like to hide "mySecretApplicationId" and "mySecretClientKey", is there private place or directory in my project where I can put this variables?
Thanks!
You can use a .plist file where you store all your important keys. It is very important to put this file into your .gitignore file.
In your case, you need to set your keys.plist file like this:
And use it inside your AppDelegate as follows:
var keys: NSDictionary?
if let path = NSBundle.mainBundle().pathForResource("Keys", ofType: "plist") {
keys = NSDictionary(contentsOfFile: path)
}
if let dict = keys {
let applicationId = dict["parseApplicationId"] as? String
let clientKey = dict["parseClientKey"] as? String
// Initialize Parse.
Parse.setApplicationId(applicationId!, clientKey: clientKey!)
}
SWIFT 3 Update:
if let path = Bundle.main.path(forResource: "Keys", ofType: "plist") {
keys = NSDictionary(contentsOfFile: path)
}
Put them in a configuration file that you add to the .gitignore file. Check in a sample configuration file that every developer can use to create their own configuration.
If you want to share your project without keys then:
Add Keys( as you prefer - enum, struct, or even object/singleton)
struct Keys {
static let sandboxToken = "Tpk_hh43nneu3jwsu3u"
static let productionToken = "pk_b5h4uend8ejwnw8"
}
In your code add follow code:
extension APIManager {
enum Environment {
case sandbox, production
var apiKey: String {
switch self {
case .sandbox:
return Keys.iexSandboxToken // <- Here
case .production:
return Keys.iexProductionToken // <- Here
}
}
}
}
or if you want to deal with optionals then you can add something similar to:
struct Keys {
static let sandboxToken: String? = "Tpk_hh43nneu3jwsu3u"
static let productionToken: String?
}
and on use add assert
var apiKey: String {
switch self {
case .sandbox:
guard let token = Keys.iexSandboxToken else {
assertionFailure("Please fill the tokent in Keys.swift")
return "anything you want"
}
return token
case .production:
guard let token = Keys.iexProductionToken else {
assertionFailure("Please fill the tokent in Keys.swift")
return "anything you want"
}
return token
}
}
So, in production, it will fail.
Add it on .gitignore. So, your keys are hidden.

Resources