Folder URL from .onDrop() modifier - swift

I want to read the contents of a folder by getting its URL from an .onDrop() modifier.
The starting point is SwiftOnTap's example on how to process an NSItemProvider, but it's not resulting in anything.
How do I extract the URL from an NSItemProvider of UTType "public.url"?
.onDrop(of: ["public.url"], isTargeted: $dropping, perform: { itemProvider in
if let item = itemProvider.first {
item.loadItem(forTypeIdentifier: "public.url", options: nil) { (folder, err) in
if let data = folder as? Data {
let droppedString = String(decoding: data, as: UTF8.self)
print(droppedString)
}
}
}
return true
})

public.url alone doesn't appear to work for folders, but if your constrain it further to public.file-url, it does work. (Tested on macOS 11.2.3):
struct ContentView: View {
#State var dropping = false
var body: some View {
VStack {
Text("Hello, world!")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.onDrop(of: ["public.file-url"], isTargeted: $dropping, perform: { itemProvider in
if let item = itemProvider.first {
_ = item.loadObject(ofClass: URL.self) { (url, error) in
if let url = url {
print("URL:", url)
}
}
}
return true
})
}
}

Related

How to call API again after change was made?

So I want to search books from google books api, but only through url query, how can I call API again when I enter the text in the search bar? How to reload the call?
I tried also with textfield onSumbit method, but nothing work.
I just want to insert value of textSearch to network.searchText and that network.searchText to insert into q=
here is my code of ContentView:
//
// ContentView.swift
// BookApi
//
// Created by Luka Šalipur on 7.6.22..
//
import SwiftUI
struct URLImage: View{
var urlString: String
#State var data: Data?
var body: some View{
if let data = data, let uiimage = UIImage(data:data) {
Image(uiImage: uiimage)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width:80, height:120)
.background(Color.gray)
} else {
Image(systemName: "book").onAppear {
fetch()
}
}
}
private func fetch(){
guard let url = URL(string: urlString) else {
return
}
let task = URLSession.shared.dataTask(with:url) { data, _, error in
self.data = data
}
task.resume()
}
}
// ContentView
struct ContentView: View {
#ObservedObject var network = Network()
#State var textSearch:String = "knjiga"
#State private var shouldReload: Bool = false
func context(){
network.searchText = self.textSearch
print(network.searchText)
}
var body: some View {
NavigationView{
List{
ForEach(network.book, id:\.self){ item in
NavigationLink{
Webview(url: URL(string: "\(item.volumeInfo.previewLink)")!)
} label: {
HStack{
URLImage(urlString: item.volumeInfo.imageLinks.thumbnail)
Text("\(item.volumeInfo.title)")
}
}
}
}
.onAppear{
context()
}
.onChange(of: textSearch, perform: { value in
self.shouldReload.toggle()
})
.searchable(text: $textSearch)
.navigationTitle("Books")
.task{
await network.loadData()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
And here is my API(network) call:
//
// Network.swift
// BookApi
//
// Created by Luka Šalipur on 7.6.22..
//
import Foundation
import SwiftUI
class Network: ObservableObject{
#Published var book = [Items]()
var searchText: String = "watermelon" {
willSet(newValue) {
print(newValue)
}
}
func loadData() async {
guard let url = URL(string: "https://www.googleapis.com/books/v1/volumes?q=\(searchText)&key=API_KEY_PRIVATE") else {
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let decodedResponse = try? JSONDecoder().decode(Books.self, from: data) {
book = decodedResponse.items
}
} catch {
print("There is an error")
}
}
}
This is a perfect candidate for the Combine framework.
In Network create a publisher which removes duplicates, debounces the input for 0.3 seconds, builds the URL, loads the data and decodes it.
I don't have your types, probably there are many errors. But this is a quite efficient way for dynamic searching. By the way your naming with regard to singular and plural form is pretty confusing.
import Combine
import SwiftUI
class Network: ObservableObject {
#Published var book = [Items]()
#Published var query = ""
private var subscriptions = Set<AnyCancellable>()
init() {
searchPublisher
.sink { completion in
print(completion) // show the error to the user
} receiveValue: { [weak.self] books in
self?.book = books.items
}
.store(in: &subscriptions)
}
var searchPublisher : AnyPublisher<Books,Error> {
return $query
.removeDuplicates()
.debounce(for: 0.3, scheduler: RunLoop.main)
.compactMap{ query -> URL? in
guard !query.isEmpty else { return nil }
guard let url = URL(string: "https://www.googleapis.com/books/v1/volumes?q=\(query)&key=API_KEY_PRIVATE") else {
return nil
}
return url
}
.flatMap { url -> AnyPublisher<Data, URLError> in
return URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.eraseToAnyPublisher()
}
.decode(type: Books.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}
In the view create the view model (must be #StateObject!)
#StateObject var network = Network()
and bind searchable to query in network
.searchable(text: $network.query)
The view is updated when the data is available in network.book
The .task modifier ist not needed
There is another version of task that runs again when a value changes task(id:priority:_:). If a task is still running when the param changes it will be cancelled and restarted automatically. In your case use it as follows:
.task(id: textSearch) { newValue in
books = await getBooks(newValue)
}
Now we have async/await and task there is no need for an ObservableObject anymore.

Import and display .usdz file in swift

I try to import some .usdz files into the app and display the 3D model using SceneView. When I display some model which already in my Xcode folder, it works. But when run the app on my phone and try to import files from my Phone, print(fileName) still can get the right file name but nothing displayed in the SceneView... Anyone can help?
#State var fileName = ""
#State var openFile = false
#State var model = Model(id: 0, modelName: "")
var body: some View {
VStack{
VStack {
Text(fileName)
.fontWeight(.bold)
Button {
openFile.toggle()
} label: {
Text("Come On")
}
}
.fileImporter(isPresented: $openFile, allowedContentTypes: [.usdz]) { result in
switch result {
case .success(let url):
_ = url.startAccessingSecurityScopedResource()
print(url)
self.fileName = url.lastPathComponent
print(fileName)
model.modelName = fileName
case.failure(let error):
print(error)
}
}
SceneView(scene: SCNScene(named: model.modelName) , options: [.autoenablesDefaultLighting,.allowsCameraControl])
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height / 2)
change SCNScene(named: model.modelName) to let scene = try? SCNScene(url: url!)
Example
struct Viewer3D: View {
#State var urlLocalModel: URL?
#State var openFile = false
var body: some View {
VStack{
VStack {
Button {
openFile.toggle()
} label: {
Text("Open File Local USDZ")
}
}
.fileImporter(isPresented: $openFile, allowedContentTypes: [.usdz]) { result in
switch result {
case .success(let url):
_ = url.startAccessingSecurityScopedResource()
self.urlLocalModel = url
print(url)
case.failure(let error):
print(error)
}
}
if (urlLocalModel != nil) {
let scene = try? SCNScene(url: urlLocalModel!)
SceneView(scene: scene, options: [.autoenablesDefaultLighting,.allowsCameraControl])
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width)
}
}
}}

Display activity indicator while fetching api data

I'm filling this Picker with data from my api
var body: some View {
NavigationView {
Form {
Section(header: Text("Pesquisar Denúncia")) {
//some code...
Picker(selection: $tipoDenunciaSelecionada, label: Text("Tipo de Denúncia")) {
ForEach(tiposDenuncia, id: \.self) {item in
Text(item.nome ?? "")
}
}.onAppear(perform: carregarTipoDenuncia)
//some more code...
}
}
For fetch data, i made this func
func carregarTipoDenuncia() {
self.estaCarregando = true
let oCodigo_Entidade = UserDefaults.standard.integer(forKey: "codigoEntidade")
guard let url = URL(string: "url here") else {
print("Erro ao conectar")
return
}
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) {data, response, error in
let decoder = JSONDecoder()
if let data = data {
do {
let response = try decoder.decode([TipoDenunciaModel].self, from: data)
DispatchQueue.main.async {
self.tiposDenuncia = response
}
return
} catch {
print(error)
}
}
}
task.resume()
}
but now i dont know to display an indicator that the data is being downloaded.
I tried adding a state boolean variable and manipulating it after response to show/hide an actionsheet but it didnt worked, the actionsheet didnt disapear.
I need the user to select at least one of the options of this picker.
How can i display some indicator that the data is loading?
As you can show any view in body add a boolean #State variable with initial value true and show a ProgressView. After loading the data set the variable to false which shows the Picker.
Move the .onAppear modifier to after Form.
var body: some View {
#State private var isLoading = true
// #State private var tipoDenunciaSelecionada ...
NavigationView {
Form {
Section(header: Text("Pesquisar Denúncia")) {
//some code...
if isLoading {
HStack(spacing: 15) {
ProgressView()
Text("Loading…")
}
} else {
Picker(selection: $tipoDenunciaSelecionada, label: Text("Tipo de Denúncia")) {
ForEach(tiposDenuncia, id: \.self) {item in
Text(item.nome ?? "")
}
}
//some more code...
}.onAppear(perform: carregarTipoDenuncia)
}
}
}
let response = try decoder.decode([TipoDenunciaModel].self, from: data)
DispatchQueue.main.async {
self.tiposDenuncia = response
self.isLoading = false
}
...
You can use a determinate SwiftUI ProgressView, as seen here.
Example usage:
import SwiftUI
struct ActivityIndicator: View {
#State private var someVar: CGFloat = 0.0 //You'll need some sort of progress value from your request
var body: some View {
ProgressView(value: someVar)
}
}

SWIFTUI Observable Object Data Task only runs once?

I have an observable object class that downloads an image from a url to display:
class ImageLoader : ObservableObject {
var didChange = PassthroughSubject<Data, Never>()
var data = Data() {
didSet {
didChange.send(data)
}
}
init(urlString:String){
guard let url = URL(string: urlString) else {return}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { return }
DispatchQueue.main.async {
self.data = data
print("imageloader1")
}
}
task.resume()
}
and I show it using:
struct ShowImage1: View {
#ObservedObject var imageLoader:ImageLoader
#State var image:UIImage = UIImage()
init(withURL url:String) {
imageLoader = ImageLoader(urlString:url)
}
var body: some View {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.edgesIgnoringSafeArea(.top)
.onReceive(imageLoader.didChange) {
data in self.image = UIImage(data: data) ?? UIImage()
}
}
The problem I'm having is this is only capable of running once, If i click off the ShowImage1 view and then click back on to it, ImageLoader doesn't run again, and I'm left with a blank page.
How can I ensure that ImageLoader Runs every time the ShowImage1 view is accessed?
EDIT:
I access ShowImage1 like this:
struct PostCallForm: View {
var body: some View {
NavigationView {
Form {
Section {
Button(action: {
if true {
self.showImage1 = true
}
}){
Text("View Camera 1 Snapshot")
}.overlay(NavigationLink(destination: ShowImage1(withURL: "example.com/1.jpg"), isActive: self.$showImage1, label: {
EmptyView()
}))
}
}
Section {
Button(action: {
}){
Text("Submit")
}
}
}.disabled(!submission.isValid)
}
}
}
import SwiftUI
import Combine
class ImageLoader : ObservableObject {
var didChange = PassthroughSubject<Data, Never>()
var data = Data() {
didSet {
didChange.send(data)
}
}
func loadImage(urlString:String) {
guard let url = URL(string: urlString) else {return}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { return }
DispatchQueue.main.async {
self.data = data
print("imageloader1")
}
}
task.resume()
}
}
struct ShowImage1Parent: View {
#State var url: String = ""
var sampleURLs: [String] = ["https://image.shutterstock.com/image-vector/click-here-stamp-square-grunge-600w-1510095275.jpg", "https://image.shutterstock.com/image-vector/certified-rubber-stamp-red-grunge-600w-1423389728.jpg", "https://image.shutterstock.com/image-vector/sample-stamp-square-grunge-sign-600w-1474408826.jpg" ]
var body: some View {
VStack{
Button("load-image", action: {
url = sampleURLs.randomElement()!
})
ShowImage1(url: $url)
}
}
}
struct ShowImage1: View {
#StateObject var imageLoader:ImageLoader = ImageLoader()
#State var image:UIImage = UIImage()
#Binding var url: String
var body: some View {
VStack{
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.edgesIgnoringSafeArea(.top)
.onReceive(imageLoader.didChange) {
data in self.image = UIImage(data: data) ?? UIImage()
}
.onChange(of: url, perform: { value in
imageLoader.loadImage(urlString: value)
})
}
}
}

TabView blank when trying to display json data via onAppear()

I followed this tutorial to call data from my API. I veered off a bit and instead used TabView to show a "home page" where data loads in the first tab. It "works" in the sense that if I navigate to another tab and go back to the home tab the data appears. When I open the app though, the tab is blank. I initially declare posts as an empty array, by why is onAppear() not populating it?
Here's the view that is supposed to be displaying my data
struct DiscoverView: View {
#ObservedObject var discoverPosts: DiscoverPosts
var body: some View {
NavigationView{
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .center){
ForEach(self.discoverPosts.posts) { post in
HStack{
DiscoverList(isModal : false,displayName : post.displayName,id : post.id,likes : post.likes,owner : post.owner,avatar : post.avatar,author_id : post.author_id,icebreaker : post.icebreaker,answer : post.answer,mediaLink : post.mediaLink,songURL : post.songURL,type : post.type,liked: post.liked)
}
.padding(10)
}
}
}
.onAppear(){
// self.discoverPosts.getPosts()
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationBarTitle("Discover")
}
}
}
here is my discoverPosts()
class discoverPosts : ObservableObject {
#State var posts : [Post] = []
func getPosts(completion: #escaping ([Post]) -> ()){
let feedURL = "URL"
guard let url = URL(string: feedURL) else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
let posts = try! JSONDecoder().decode([Post].self, from: data!)
DispatchQueue.main.async {
self.posts = posts
completion(posts)
}
}
.resume()
}
}
my ConventView.swift that shows the TabView. I believe the issue could be the hierarchy
struct ContentView: View {
var body: some View {
Home()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct Home : View {
#State var show = false
#State var status = UserDefaults.standard.value(forKey: "status") as? Bool ?? false
var body: some View{
VStack{
if self.status{
TabView {
DiscoverView(discoverPosts: DiscoverPosts())
.tabItem(){
Image(systemName: "person")
.font(.system(size:20))
}
.tag(1)
InboxView(offsetLine: IndexSet.Element())
.tabItem(){
Image(systemName: "message")
.font(.system(size: 20))
}
.tag(2)
ProfileView()
.tabItem(){
Image(systemName: "person")
.font(.system(size: 20))
}
.tag(3)
}
.accentColor(Color.purple)
.navigationViewStyle(StackNavigationViewStyle())
}
else{
ZStack{
NavigationLink(destination: SignUp(show: self.$show), isActive: self.$show) {
Text("")
}
.hidden()
Login(show: self.$show)
}
}
}
.navigationBarTitle("")
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.onAppear {
NotificationCenter.default.addObserver(forName: NSNotification.Name("status"), object: nil, queue: .main) { (_) in
self.status = UserDefaults.standard.value(forKey: "status") as? Bool ?? false
}
}
}
}
For those that experience this, You can throw Text("").frame(width: UIScreen.main.bounds.width) at the bottom of ScrollView
Change your ObservableObject to:
class DiscoverPosts: ObservableObject { // make Types capitalised
#Published var posts: [Post] = [] // <- replace #State with #Published
init() {
getPosts()
}
func getPosts() { // <- no need for completion handler, update `self.posts`
let feedURL = "URL"
guard let url = URL(string: feedURL) else { return }
URLSession.shared.dataTask(with: url) { data, _, _ in
let posts = try! JSONDecoder().decode([Post].self, from: data!)
DispatchQueue.main.async {
self.posts = posts
}
}
.resume()
}
}
and use it in your view like this:
struct DiscoverView: View {
#ObservedObject var discoverPosts: DiscoverPosts // declare only
var body: some View {
ZStack {
...
}
//.onAppear { // <- remove onAppear
// self.discoverPosts.getPosts()
//}
}
}
You also need to pass DiscoverPosts to DiscoverView from Home view:
DiscoverView(discoverPosts: DiscoverPosts())
Note that if you previously accessed self.posts in your view, you will now need to access self.discoverPosts.posts