Hello,
I am trying to use the SwiftUI fileImporter to get the URL of a direcotry and access it for the files in it.
If I follow the document( Access the directory’s content ) and use url.startAccessingSecurityScopedResource for each file, it always returns false. but this seems to be different from the documentation. If the current behavior is correct, will the documentation be updated in the future?
Related: Access all files in UIDocumentPick… | Apple Developer Forums
I asked this question because I am concerned that if I remove the standardAccessingSecurityScopedResource to match the current situation, the standardAccessingSecurityScopedResource may become necessary due to future iOS updates.
Also, is there any way to know if the status of the AccessingSecurityScopedResource?
It would be helpful if we could callstartAcesingSecurityScopedResource only when needed.
Thanks
Here is a sample code
@main
struct SampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@State private var isShowingImportFolder = false
@State private var isShowingImportFile = false
var body: some View {
VStack(spacing: 48) {
Button("Read Folder") {
isShowingImportFolder = true
}
.fileImporter(isPresented: $isShowingImportFolder, allowedContentTypes: [.folder]) { result in
switch result {
case .success(let url):
processDirectory(url)
case .failure(let error):
print("failed. error: \(error)")
}
}
Button("Read File") {
isShowingImportFile = true
}
.fileImporter(isPresented: $isShowingImportFile, allowedContentTypes: [.data]) { result in
switch result {
case .success(let url):
readFile(url)
case .failure(let error):
print("failed. error: \(error)")
}
}
}
}
private func processDirectory(_ directory: URL) {
guard directory.startAccessingSecurityScopedResource() else {
fatalError("failed. directory.startAccessingSecurityScopedResource")
}
defer { directory.stopAccessingSecurityScopedResource() }
var error: NSError?
NSFileCoordinator().coordinate(readingItemAt: directory, error: &error) { url in
let urls = try! FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: [.nameKey, .isDirectoryKey])
urls.lazy
.filter { !$0.hasDirectoryPath }
.forEach { readFile($0) }
}
}
private func readFile(_ url: URL) {
guard url.startAccessingSecurityScopedResource() else {
print("failed access. \(url.path)")
return
}
defer { url.stopAccessingSecurityScopedResource() }
let data = try! Data(contentsOf: url)
print("file.path: \(url.path()), size: \(data.count)")
// read file...
}
}
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Hello,
I'm encountering an issue where my released app fails to launch only on iOS 17.4. The version of the app released through TestFlight works fine without any issues.
Specifically, when the app installed from the App Store is launched on iOS 17.4, it immediately crashes. However, I've noticed the following:
If I turn off the network connection, such as putting the device in Airplane Mode, the app launches successfully.
Once the app is launched, I can re-enable the network connection, and the app continues to run without crashing.
My app uses StoreKit2 for handling transactions and connections with the App Store. It initiates a connection to the App Store via StoreKit2 at launch. The primary difference between the TestFlight version and the production version is the App Store endpoint they connect to. This leads me to suspect that there might be an issue with the connection to the App Store.
(Another possibility is that the app communicates with Firebase or Google Admob, so there could be an issue with these SDKs as well.)
This issue only occurs in the production version, making it difficult to investigate. Are there any suggestions on what I can do to further diagnose this issue?
You can download my app from here:
https://apps.apple.com/us/app/repeatable-player-cut-loop/id616310281
I can provide the TestFlight URL if needed.
Any help or guidance would be greatly appreciated.
This is a question regarding the specification of ObservalObject.
The following code does not cause a compilation error when the deployment target is set to iOS 16, but it does cause the following error when set to iOS 15:
class Model: ObservableObject {
let player: AVPlayer
@Published var isPlaying = false
var playerObserver: NSKeyValueObservation?
init() {
self.player = AVPlayer()
self.playerObserver = self.player.observe(\.rate, options: [.new, .old]) { player, change in
NSLog("changed rate: \(String(describing: change.newValue))")
}
}
func setup() {
let name = "sample"
let url = Bundle.main.url(forResource: name, withExtension: "m4a")!
let playerItem = AVPlayerItem(url: url)
self.player.replaceCurrentItem(with: playerItem)
}
func play() {
self.player.play()
self.isPlaying = true
}
}
The following error occurs in iOS 15:
Cannot form key path to main actor-isolated property 'rate'
Call to main actor-isolated instance method 'play()' in a synchronous nonisolated context
Additionally, if the code is modified as follows, the error does not occur even in iOS 15. Specifically, adding @MainActor to the Model resolves the issue.
@MainActor
class Model: ObservableObject {
...
}
From this behavior, I guessed that ObservableObject is implicitly a MainActor in iOS 16 and later. Is this understanding correct?
Hello,
I have a simple example using StateObject and List. When I bind the List(selection:) to a property of the StateObject like this:
List(selection: $viewModel.selectedIndex) { ... }
I noticed that each time I push the view using a NavigationLink, a new instance of the StateObject is created. However, when I pop the view, the deinit of the StateObject is not called.
When is deinit actually expected to be called in this case?
Example code:
import SwiftUI
@main
struct NavigationViewDeinitSampleApp: App {
var body: some Scene {
WindowGroup {
NavigationStack {
ContentView()
}
}
}
}
struct Item: Hashable {
let text: String
}
@MainActor
fileprivate class ContentViewModel: ObservableObject {
@Published var selectedIndex: Int? = nil
init() {
NSLog("ContentViewModel.init")
}
deinit {
NSLog("ContentViewModel.deinit")
}
}
struct ContentView: View {
@StateObject private var model = ContentViewModel()
let items: [Item] = {
return (0...10).map { i in
Item(text: "\(i)")
}
}()
var body: some View {
List(selection: $model.selectedIndex) {
ForEach(items.indices, id: \.self) { idx in
let item = items[idx]
NavigationLink {
ContentView()
} label: {
Text(item.text)
}
}
}
}
}
Interestingly, if I instead use a plain @State variable inside the View:
@State private var selectedIndex: Int?
...
List(selection: $selectedIndex) { ... }
Then the deinit of the StateObject does get called when the view is popped.
Because there's no sign of deinit being triggered in the first pattern, I’m starting to suspect this might be a SwiftUI bug. Has anyone seen this behavior or have more information about it?
Thanks in advance.
Environment:
Xcode: 16.4(16F6)
iOS Simulator: iPhone SE3 iOS16.4(20E247),iPhone SE3 iOS 18.4(22E238)
I have a Model class containing the main data (@Published var counter: Int), which is automatically updated periodically by a Timer. I am considering displaying the Model.counter value in a SheetView.
I believe a simple case would look like this.
@main
struct BindingSampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
@MainActor
class ContentModel: ObservableObject {
@Published var counter: Int = 0
@Published var isShowingSheet = false
init() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
Task { @MainActor in
self.counter += 1
print("counter: \(self.counter)")
}
}
}
func showSheet() {
isShowingSheet = true
}
}
struct ContentView: View {
@StateObject var model = ContentModel()
var body: some View {
VStack {
Button() {
model.showSheet()
} label: {
Text("Show. \(model.counter)")
}
}
.sheet(isPresented: $model.isShowingSheet) {
SheetView(counter: model.counter)
}
.padding()
}
}
struct SheetView: View {
let counter: Int
var body: some View {
VStack {
Text("\(counter)")
.font(.largeTitle)
}
}
}
Due to the increasing complexity of SheetView's logic, I've created a separate SheetModel. What I want to achieve is to display the value of Model.counter, which is updated by a timer, in SheetView by relaying it through SheetModel.
After some trial and error, I have come up with the following two pieces of code that seem to achieve the desired behavior. However, I am not sure if this is the appropriate way to use SwiftUI.
Specifically, for the first code snippet, self.sheetModel = SheetModel(counter: self._counter), I suspect it may not be ideal because it probably causes both Model and SheetModel to reference the same object.
For the second snippet, SheetModel.init(counter: Published.Publisher) { counter.assign(to: &self.$counter) }, it feels a bit verbose.
Is there a simpler and better way to bind Model.counter and SheetModel.counter?
(Tested on Xcode Version 14.3.1 (14E300c), iOS simulator 16.4)
First
@main
struct BindingSampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
@MainActor
class ContentModel: ObservableObject {
@Published var counter: Int = 0
@Published var sheetModel: SheetModel?
var isShowingSheet: Bool {
get {
sheetModel != nil
}
set {
if !newValue {
sheetModel = nil
}
}
}
init() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
Task { @MainActor in
self.counter += 1
print("counter: \(self.counter)")
}
}
}
func showSheet() {
// I suspect it may not be ideal because it probably causes both Model and
// SheetModel to reference the same object.
self.sheetModel = SheetModel(counter: self._counter)
}
}
@MainActor
class SheetModel: ObservableObject {
@Published var counter: Int = 0
init(counter: Published<Int>) {
print("SheetModel.init")
self._counter = counter
}
}
struct ContentView: View {
@StateObject var model = ContentModel()
var body: some View {
VStack {
Button() {
model.showSheet()
} label: {
Text("Show. \(model.counter)")
}
}
.sheet(isPresented: $model.isShowingSheet) {
SheetView(model: model.sheetModel!)
}
.padding()
}
}
struct SheetView: View {
@ObservedObject var model: SheetModel
var body: some View {
VStack {
Text("\(model.counter)")
.font(.largeTitle)
}
}
}
Second
@MainActor
class ContentModel: ObservableObject {
@Published var counter: Int = 0
@Published var sheetModel: SheetModel?
var isShowingSheet: Bool {
get {
sheetModel != nil
}
set {
if !newValue {
sheetModel = nil
}
}
}
init() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
Task { @MainActor in
self.counter += 1
print("counter: \(self.counter)")
}
}
}
func showSheet() {
self.sheetModel = SheetModel(counter: $counter)
}
}
@MainActor
class SheetModel: ObservableObject {
@Published var counter: Int = 0
init(counter: Published<Int>.Publisher) {
print("SheetModel.init")
// it feels a bit verbose.
counter.assign(to: &self.$counter)
}
}
// ContentView and SheetView are same as above.