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

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)

Related

Check zip file containing video content or not in swift

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)
}

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

IOS editor bug. archivedData renamed

Please help me! I am stuck in a loop and can't find my way out. I am trying to learn IOS programming for work so I thought I would start with their tutorial app the Meal list application. I am at the part where you are supposed to start saving persistent data and now the editor has me stuck in a never ending loop. I have a line of code...
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(meals, toFile: Meal.ArchiveURL.path)
That gives me a warning that says...
'archiveRootObject(_:toFile:)' was deprecated in iOS 12.0: Use
+archivedDataWithRootObject:requiringSecureCoding:error: instead
OK, so I change the line of code to...
let isSuccessfulSave = NSKeyedArchiver.archivedDataWithRootObject(meals)
Which then gives me the warning...
'archivedDataWithRootObject' has been renamed to
'archivedData(withRootObject:)'
OK, so I change the line of code to...
let isSuccessfulSave = NSKeyedArchiver.archivedData(withRootObject: meals)
Which tells me...
'archivedData(withRootObject:)' was deprecated in iOS 12.0: Use
+archivedDataWithRootObject:requiringSecureCoding:error: instead
OK... So... archivedData was deprecated and I have to use archivedDataWithRootObject, but using archivedDataWithRootObject has been renamed to archivedData, but archivedData is deprecated so use archivedDataWithRootObject which is renamed to archivedData which is deprecated... ad infinitum.
I have tried looking on the developer docs but they just tell me the same thing, one is deprecated, with no links or anything and searching google just gives me a bunch of pages showing me the syntax of using any of them. I am still really new to IOS programming and have no idea how to get out of this endless loop of deprecated to renamed to deprecated to...
Please help, I am lost and not sure how to continue. Thank you.
I am following the same example you are trying to do, and I figured out how to update the methods for storing and retrieving values in iOS 12, this should help you:
//MARK: Private Methods
private func saveMeals() {
let fullPath = getDocumentsDirectory().appendingPathComponent("meals")
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: meals, requiringSecureCoding: false)
try data.write(to: fullPath)
os_log("Meals successfully saved.", log: OSLog.default, type: .debug)
} catch {
os_log("Failed to save meals...", log: OSLog.default, type: .error)
}
}
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
private func loadMeals() -> [Meal]? {
let fullPath = getDocumentsDirectory().appendingPathComponent("meals")
if let nsData = NSData(contentsOf: fullPath) {
do {
let data = Data(referencing:nsData)
if let loadedMeals = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Array<Meal> {
return loadedMeals
}
} catch {
print("Couldn't read file.")
return nil
}
}
return nil
}
Also you will find that you need to update ViewDidLoad as this:
override func viewDidLoad() {
super.viewDidLoad()
// Use the edit button item provided by the table view controller.
navigationItem.leftBarButtonItem = editButtonItem
let savedMeals = loadMeals()
if savedMeals?.count ?? 0 > 0 {
meals = savedMeals ?? [Meal]()
} else {
loadSampleMeals()
}
}
I hope this helps, for me the app is now working, storing and retrieving data.
FYI: This doesn't work with Xcode 11 beta and iOS 13 is should work with anything before those versions.
A general solution for iOS 12 would be:
class SettingsArchiver {
static func setData(_ value: Any, key: String) {
let ud = UserDefaults.standard
let archivedPool = try? NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true)
ud.set(archivedPool, forKey: key)
}
static func getData<T>(key: String) -> T? {
let ud = UserDefaults.standard
if let val = ud.value(forKey: key) as? Data,
let obj = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(val) as? T {
return obj
}
return nil
}
}
You need
try {
let data = try NSKeyedArchiver.archivedData(withRootObject:meals,requiringSecureCoding:true)
try data.write(to:fullPath)
}
catch {
print(error)
}
Here in Docs it's IOS 11+
I would say, the answer directly addressing your question is to use the ArchiveURL defined in your Meal.swift data model (think MVC pattern) and reimplement the saveMeals() function in your MealTableViewController.swift controller using the recommended replacement to the deprecated archiveRootObject method this way:
private func saveMeals(){
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: meals, requiringSecureCoding: true)
try data.write(to: Meal.ArchiveURL)
}
catch {
print("Couldn't save to file")
}
}
Although this answer is a little late :-) I hope it helps whomever may come across this issue.

iOS11 How to handle a custom file dropped in a custom view

I've been messing around with the drop feature of iOS11. I found a lot of examples with the standard UIImage etc but nothing with custom files.
The drop part works pretty well, I use it to drop a custom file and import it into my app.
The problem is that I only get the Data of this file and I would like to get its name and extension for example.
I don't know if my way of handling the custom file dropped is how it should be to obtain this information. And if so, how do you get this information from the file?
I have a DropFile class conforming to the NSItemProviderReading protocol.
class DropFile : NSObject, NSItemProviderReading {
let fileData:Data?
required init(data:Data, typeIdentifier:String) {
fileData = data
}
static var readableTypeIdentifiersForItemProvider: [String] {
var documentTypeArray: [String] = []
for ext in FileImportProcessHelper.sharedInstance.getImportDocumentType() {
let UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext as CFString, nil)
documentTypeArray.append((UTI?.takeRetainedValue() as String?)!)
}
return documentTypeArray
}
static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> Self {
return self.init(data: data, typeIdentifier: typeIdentifier)
}
}
This is my ViewController conforming to the UIDropInteractionDelegate
// MARK: Handling Drop
#available(iOS 11.0, *)
// We refuse the dropped items based on their UTI
func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
var documentTypeArray: [String] = []
for ext in FileImportProcessHelper.sharedInstance.getImportDocumentType() {
let UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext as CFString, nil)
documentTypeArray.append(UTI?.takeRetainedValue() as! String)
}
return session.hasItemsConforming(toTypeIdentifiers: documentTypeArray) && session.items.count == 1
}
#available(iOS 11.0, *)
func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
// Copy file from source app
return UIDropProposal(operation: .copy)
}
#available(iOS 11.0, *)
func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
// Consume drag items
session.loadObjects(ofClass: DropFile.self) { items in
if let fileItems = items as? [DropFile] {
DragnDropManager.sharedManager.createDropObject(fileItems: fileItems)
}
}
}
And then how I write my file.
func createDropObject(fileItems: [DropFile]) {
let documentsPathString = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
let inbox = URL(fileURLWithPath: documentsPathString!).appendingPathComponent("UserDropInbox/")
do {
try FileManager.default.createDirectory(atPath: inbox.path, withIntermediateDirectories: true, attributes: nil)
} catch let error as NSError {
NSLog("Unable to create directory \(error.debugDescription)")
}
for file in fileItems {
do {
let dropFilePath = inbox.appendingPathComponent("File").appendingPathExtension("pdf")
try file.fileData?.write(to:dropFilePath)
} catch {
NSLog(error as! String)
}
}
FileImportInboxManager.shared.hasReceivedFiles = true;
}
I admit, it's not straightforward :
you can list all the UIDragItems dragged by the user in the UIDropSession items member
each of these items have a NSItemProvider itemProvider member
this item provider has an optional String? suggestedName member that may not be nil
A simple loop to print all the session's items suggested names :
for item in session.items {
if let name = item.itemProvider.suggestedName {
print(name)
}
}
Practically, when files come from the File app it will provide the name of the file without its extension, but given you can easily access its UTI, you can append an extension that will be close to the original one (jpeg instead of jpg for example).

How to get the data from file on Icloud Swift

I'm creating an app that gets a file from Icloud and converts it into a b64 format string, but I have a problem:
I really don't know how to get the data from this file. I thought that could be easy opening the Path from the imported file from ICloud but It returns nil when I try to acess to the route.
My code example is here, as you can see I have the temp route :( (file:///.../Aplication/xxx-xxx-xxx-xx/temp/com.domain.AppName/Carolina.cer):
extension KeyViewController: UIDocumentMenuDelegate {
func documentMenu(documentMenu: UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController) {
documentPicker.delegate = self
print ("documentMenu")
self.presentViewController(documentPicker, animated: true, completion: nil)
}
}
extension KeyViewController: UIDocumentPickerDelegate {
func documentPicker(controller: UIDocumentPickerViewController, didPickDocumentAtURL url: NSURL) {
print("url string")
print(String(url))
//prints file:///.../Aplication/xxx-xxx-xxx-xx/temp/com.domain.AppName/Carolina.cer
if controller.documentPickerMode == UIDocumentPickerMode.Import {
if (NSFileManager.defaultManager().fileExistsAtPath(String(url))){
print("exists")
}
else{
print("not exists")
}
dispatch_async(dispatch_get_main_queue()) {
if let fileName = url.lastPathComponent {
var fileNameArray = fileName.componentsSeparatedByString(".")
print ("fileNameArray")
print (fileNameArray)
if ((fileNameArray[1] != "cer") && (fileNameArray[1] != "key")) {
self.alertMessage = "Not a valid selection, choose .cer or .key Files"
self.showAlertMessage(self.alertMessage)
}
else{
//saving the name
if (fileNameArray[1]=="cer")
{
NSUserDefaults.standardUserDefaults().setObject(fileName, forKey: "temporalCerFile")
}
else{
//saving the name
NSUserDefaults.standardUserDefaults().setObject(fileName, forKey: "temporalKeyFile")
}
NSUserDefaults.standardUserDefaults().synchronize()
}
}
}
}
}
}
How can I get the content inside the file?. Please, hope you can help me
Your code isn't working because you are passing the incorrect value to fileExistsAtPath:.
The proper way to convert a file NSURL to a file string is to use the path() method.
if (NSFileManager.defaultManager().fileExistsAtPath(url.path())) {
It might be url.path instead of url.path(). I'm not sure about the older Swift syntax. In Swift 3 it would be:
if FileManager.default.fileExists(atPath: url.path) {
Once you know it exists, that are many ways to load the file. It all depends on what you need to do with it. But the first requirement is to move the file from the given url to a location inside your app's sandbox. At least if you need the file to survive the next use of your app. If you just need to look at the file this one time, you don't need this step.

Resources