My app is running fine but as soon the screen safe is on or the doing something else on the iphone the stream stops. I activated the background modes "is playing audio" but it does not helps.
This is my ViewController.swift
import UIKit
import MediaPlayer
class ViewController: UIViewController {
let player: MPMoviePlayerViewController = MPMoviePlayerViewController(contentURL: NSURL(string: "http://url to my stream"))
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
player.moviePlayer.movieSourceType = .Streaming
self.presentViewController(player, animated: true, completion: nil)
loadAddressURL()
}
func stop() {
player.moviePlayer.stop()
}
#IBAction func Hitplay(sender: AnyObject) {
player.moviePlayer.play()
}
#IBAction func Hitpause(sender: AnyObject) {
player.moviePlayer.stop()
}
#IBOutlet var Nowplay: UIWebView!
var URLPath = "http://url to on air now"
func loadAddressURL() {
let requestURL = NSURL (string:URLPath)
let request = NSURLRequest (URL: requestURL!)
Nowplay.loadRequest(request)
}
}
and here is my info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList- 1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>com.product name.$(PRODUCT_NAME:rfc1034identifier)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
</dict>
Setting the audio background mode is correct, but I think you also need to set the audio category for the audio session.
Try adding this to your app delegate's didFinishLaunchingWithOptions:
var activeError: NSError? = nil
AVAudioSession.sharedInstance().setActive(true, error: &activeError)
if let actError = activeError {
NSLog("Error setting audio active: \(actError.code)")
}
var categoryError: NSError? = nil
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: &categoryError)
if let catError = categoryError {
NSLog("Error setting audio category: \(catError.code)")
}
Related
I have a React Native project and I am trying to build Share extension using Xcode and swift. I have tried using https://github.com/meedan/react-native-share-menu but it won't work for me. I have tried other lib as well but they are not maintained properly so I decided to build one of my own.
I only want to handle urls and text in my app.
I first create a Share extension and named it as Share
Following is my info.plist file for Share extension
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Share</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
</dict>
<key>NSExtensionPrincipalClass</key>
<string>Share.ShareViewController</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
</dict>
</plist>
Following is my code for ShareViewController
import UIKit
import Social
import CoreServices
class ShareViewController: UIViewController {
private let typeText = String(kUTTypeText)
private let typeURL = String(kUTTypeURL)
private let appURL = "leaaoShare://"
private let groupName = "group.leaaoShare"
private let urlDefaultName = "incomingURL"
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// 1
guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem,
let itemProvider = extensionItem.attachments?.first else {
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
return
}
if itemProvider.hasItemConformingToTypeIdentifier(typeText) {
handleIncomingText(itemProvider: itemProvider)
} else if itemProvider.hasItemConformingToTypeIdentifier(typeURL) {
handleIncomingURL(itemProvider: itemProvider)
} else {
print("Error: No url or text found")
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
}
}
private func handleIncomingText(itemProvider: NSItemProvider) {
print("here22")
itemProvider.loadItem(forTypeIdentifier: typeText, options: nil) { (item, error) in
if let error = error { print("Text-Error: \(error.localizedDescription)") }
if let text = item as? String {
do {// 2.1
let detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector.matches(
in: text,
options: [],
range: NSRange(location: 0, length: text.utf16.count)
)
// 2.2
if let firstMatch = matches.first, let range = Range(firstMatch.range, in: text) {
self.saveURLString(String(text[range]))
}
} catch let error {
print("Do-Try Error: \(error.localizedDescription)")
}
}
self.openMainApp()
}
}
private func handleIncomingURL(itemProvider: NSItemProvider) {
print("here")
itemProvider.loadItem(forTypeIdentifier: typeURL, options: nil) { (item, error) in
if let error = error { print("URL-Error: \(error.localizedDescription)") }
if let url = item as? NSURL, let urlString = url.absoluteString {
self.saveURLString(urlString)
}
self.openMainApp()
}
}
private func saveURLString(_ urlString: String) {
UserDefaults(suiteName: self.groupName)?.set(urlString, forKey: self.urlDefaultName)
}
private func openMainApp() {
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: { _ in
guard let url = URL(string: self.appURL) else { return }
_ = self.openURL(url)
})
}
// Courtesy: https://stackoverflow.com/a/44499222/13363449 👇🏾
// Function must be named exactly like this so a selector can be found by the compiler!
// Anyway - it's another selector in another instance that would be "performed" instead.
#objc private func openURL(_ url: URL) -> Bool {
var responder: UIResponder? = self
while responder != nil {
if let application = responder as? UIApplication {
return application.perform(#selector(openURL(_:)), with: url) != nil
}
responder = responder?.next
}
return false
}
}
Following is my AppDelegate code
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if let scheme = url.scheme,
scheme.caseInsensitiveCompare("leaaoShare") == .orderedSame,
let page = url.host {
var parameters: [String: String] = [:]
URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems?.forEach {
parameters[$0.name] = $0.value
}
print("redirect(to: \(page), with: \(parameters))")
// for parameter in parameters where parameter.key.caseInsensitiveCompare("url") == .orderedSame {
// UserDefaults().set(parameter.value, forKey: "incomingURL")
// }
}
return true
}
I have also created App groups for my main and share extension project
When I try to share url from safari, I am able to see my app in the share sheet but when I click on my app nothing happens. It feels like my app crashes or something. I don't see anything in Xcode console as well. Also when I click my app from share sheet , I cannot see the post dialog which iOS shows when sharing urls. Same issue happens when sharing text
I created a small native iOS app with Swift and it is available at https://github.com/PritishSawant/iosDevShare. I am following this article https://medium.com/#damisipikuda/how-to-receive-a-shared-content-in-an-ios-application-4d5964229701 to create extension
I figured out the issue. I forgot to add following in my main app's info plist
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>leaaoShare</string>
</array>
</dict>
</array>
I know I am not the only person having this issue,
Basiclly it seems like SwiftUI can't request Location Always even if it is inside the info.plist you instead have to add a call to it later on (which I have done.)
The issue I can see happening is that people won't change it from while app in use to always.
So how could one make a background processor to keep updating it? - I see Apple allows a background task to be ran that's all
my info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Delivering location based Fuel Price Updates, Weather, News & Sports. As well as local offers.</string>
<key>NSLocationUsageDescription</key>
<string>Delivering location based Fuel Price Updates, Weather, News & Sports. As well as local offers.</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>location</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UIStatusBarHidden</key>
<false/>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
LocationServices.swift
//
// LocationManager.swift
// CoreLocationDemo
//
// Created by Sheikh Bayazid on 7/18/20.
// Copyright © 2020 Sheikh Bayazid. All rights reserved.
//
import Foundation
import CoreLocation
import Combine
import UIKit
import SwiftUI
class LocationManager: NSObject, CLLocationManagerDelegate, ObservableObject {
private let manager: CLLocationManager
static var LMlat = 0.0
static var LMlong = 0.0
#Published var lastKnownLocation: CLLocation?
// var getLat: String {
// return "\(lastKnownLocation?.coordinate.latitude)"
// }
// var getLon: String {
// return "\(lastKnownLocation?.coordinate.longitude)"
// }
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .denied{
print("denied")
}
else{
print("athorized")
manager.requestLocation()
}
}
func start() {
//manager.requestAlwaysAuthorization()
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
}
init(manager: CLLocationManager = CLLocationManager()) {
self.manager = manager
super.init()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
func startUpdating() {
self.manager.delegate = self
// self.manager.requestAlwaysAuthorization()
self.manager.requestWhenInUseAuthorization()
self.manager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
lastKnownLocation = locations.last
// print(lastKnownLocation!.coordinate.latitude)
self.manager.requestAlwaysAuthorization()
if(lastKnownLocation!.coordinate.latitude != LocationManager.LMlat && lastKnownLocation!.coordinate.longitude != LocationManager.LMlong)
{
print("Last Known Location Is not a match")
LocationManager.LMlat = lastKnownLocation!.coordinate.latitude
LocationManager.LMlong = lastKnownLocation!.coordinate.longitude
updateServerLocation(latitude: LocationManager.LMlat, longitude: LocationManager.LMlong)
}
/* Maybe Use in Future Version
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: lastKnownLocation!.coordinate.latitude, longitude: lastKnownLocation!.coordinate.longitude)
geoCoder.reverseGeocodeLocation(location, completionHandler:
{
placemarks, error -> Void in
// Place details
guard let placeMark = placemarks?.first else { return }
// Location name
if let locationName = placeMark.location {
print(locationName)
}
// Street address
if let street = placeMark.thoroughfare {
print(street)
}
// City
if let city = placeMark.subAdministrativeArea {
print(city)
}
// Zip code
if let zip = placeMark.isoCountryCode {
print(zip)
}
// Country
if let country = placeMark.country {
print(country)
}
})
*/
//showLocation()
}
func updateServerLocation(latitude:Double,longitude:Double)
{
let locationurl = URL(string: "https://EXAMPLE.com/lat=\(latitude)&long=\(longitude)")!
//print(locationurl )
// print("location: \(MusicPlayer.uuid ?? "") lat: \(latitude), long: \(longitude)")
URLSession.shared.dataTask(with: locationurl) { (data, res, err) in
DispatchQueue.main.async{
// print("The Server should of updated")
// guard let data = data else { return }
}
return
}.resume()
}
// func showLocation(){
// print("From showLocation method")
// print("Latitude: \(getLat)")
// print("Longitude: \(getLon)")
// }
}
and in my ContentView.Swift
var lat: String{
return "\(location.lastKnownLocation?.coordinate.latitude ?? 0.0)"
}
var lon: String{
return "\(location.lastKnownLocation?.coordinate.longitude ?? 0.0)"
}
init() {
self.location.startUpdating()
}
You have a few things that I suggest you do to improve your use of location.
Firstly, it seems that you may be getting confused by the fact that when you ask for "always" location permission on iOS 13, the user actually gets prompted for "when in use". On iOS 13.4 you can trigger a prompt for "always" by first requesting (and receiving) "when in use" and then asking for "always". There are cases where this is the right approach, but I don't think your app is one of them.
You should start by watching What's new in Core Location from WWDC 2019. It explains how provisional-always location permission works.
Looking at your code, it seems that you are trying to re-invent significant location change monitoring. Core Location can do this for you, providing an update only when the user has moved 500m or more; this sounds like it would suit your use case.
Also, based on your use case I can't see that you actually need "always" permission on ios13+; you can use "when in use" for significant location change monitoring with background mode enabled. You can stop background location updates when the user stops the audio stream; there is no good reason for your app to have access to the user's location when they aren't playing audio.
Note that in iOS 12 and earlier you will need to ask for "always" permission, but since you mention SwiftUI, presumably iOS 13 is your minimum supported version.
Finally, with regard to Swift UI, an object like LocationServices should be created in your scene delegate and injected into the environment, not created by a view.
My app just wont print anything at all. I have added print statements but none of them print. Its a problem that occurred earlier too, but I ignored. Maybe I'm doing some HTTP request wrong, although I don't find any. Here is my code;
import UIKit
class ViewController: UIViewController {
#IBOutlet var cityName: UITextField!
#IBOutlet var results: UILabel!
#IBAction func searchButton(_ sender: Any) {
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = URL(string: "http://www.weather-forecast.com/locations/KolKata/forecasts/latest")!
let request = NSMutableURLRequest(url: url)
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
if error != nil {
print(error!)
}
else {
if let unwrapped = data {
let datastring = NSString(data: unwrapped, encoding: String.Encoding.utf8.rawValue)
print(datastring!)
}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
If it maybe the security purposes I add the info.plist file for reference too:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>weather-forecast.com</key>
<dict>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludeSubdomains</key>
<true/>
</dict>
</dict>
</dict>
The problem is not with info.plist you need to write task.resume(). Stop using force unwrap also
guard let url = URL(string: "http://www.weather-forecast.com/locations/KolKata/forecasts/latest") else {return}
let request = NSMutableURLRequest(url: url)
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
if error != nil {
print(error!)
}
else {
if let unwrapped = data {
if let datastring = NSString(data: unwrapped, encoding: String.Encoding.utf8.rawValue){
print(datastring)
}
}
}
}
task.resume()
}
I'm trying to play a video from URL, but when I run the code, all I can see is the play button crossed out.
here is the code:
import UIKit
import AVFoundation
import AVKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
#IBAction func playVideo(_ sender: Any) {
let videoURL = URL(string: "http://techslides.com/demos/sample-videos/small.mp4")
let player = AVPlayer(url: videoURL!)
let playerControler = AVPlayerViewController()
playerControler.player = player
present(playerControler, animated: true) {
player.play()
}
}
Xcode 9.1
Swift 4
Any idea what is wrong?
You need to edit your .plist file to be able to get video by given http URL
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
Add this code to your .plist xml, or simply try to load https: video:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>example.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
I'm trying to export my UIDocument subclass with a UIDocumentPickerViewController. The subclass writes data to a FileWrapper and its UTI conforms to com.apple.package.
But the presented document picker shows "Documents in iCloud Drive are not available because the iCloud Drive setting is disabled."
The document is successfully written to the cache, as I can see from the exported container package.
When I change the document subclass and custom UTI to conform to a single file (e.g. public.plain-text), the document picker works fine and I can export the file. So the problem seems to be with the Document Type or Exported UTI.
Am I doing something wrong or is this a bug?
Info.plist
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>Custom Doc</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.zxzxlch.documentsandbox.customdoc</string>
</array>
<key>LSTypeIsPackage</key>
<true/>
</dict>
</array>
...
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>com.apple.package</string>
</array>
<key>UTTypeDescription</key>
<string>Custom Doc File</string>
<key>UTTypeIdentifier</key>
<string>com.zxzxlch.documentsandbox.customdoc</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>zzz</string>
</array>
</dict>
</dict>
</array>
CustomDocument.swift
private let textFilename = "contents.txt"
class CustomDocument: UIDocument {
var content = "Test"
override func load(fromContents contents: Any, ofType typeName: String?) throws {
guard let topFileWrapper = contents as? FileWrapper,
let textData = topFileWrapper.fileWrappers?[textFilename]?.regularFileContents else {
return
}
content = String(data: textData, encoding: .utf8)!
}
override func contents(forType typeName: String) throws -> Any {
let textFileWrapper = FileWrapper(regularFileWithContents: content.data(using: .utf8)!)
textFileWrapper.preferredFilename = textFilename
return FileWrapper(directoryWithFileWrappers: [textFilename: textFileWrapper])
}
}
ViewController.swift
func exportDocument() {
// Write to cache
let cachesDir = FileManager.default.urls(for: FileManager.SearchPathDirectory.cachesDirectory, in: .allDomainsMask).first!
let dataDir = cachesDir.appendingPathComponent("export", isDirectory: true)
try! FileManager.default.createDirectory(at: dataDir, withIntermediateDirectories: true, attributes: nil)
let fileURL = dataDir.appendingPathComponent("cookie").appendingPathExtension("zzz")
let archive = CustomDocument(fileURL: fileURL)
archive.content = "Cookie cat"
archive.save(to: archive.fileURL, for: .forCreating) { success in
guard success else {
let alertController = UIAlertController.notice(title: "Cannot export data", message: nil)
self.present(alertController, animated: true, completion: nil)
return
}
let documentPicker = UIDocumentPickerViewController(url: archive.fileURL, in: .exportToService)
documentPicker.delegate = self
self.present(documentPicker, animated: true, completion: nil)
}
}
This solves my problem: make the UTI also conform to public.composite-content, i.e.
<key>UTTypeConformsTo</key>
<array>
<string>com.apple.package</string>
<string>public.composite-content</string>
</array>
I'm not sure why though.