Import and display .usdz file in swift - 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)
}
}
}}

Related

How to select multiple photos using swiftUI and show that selected photos in the screen?

My app is crashing after adding multiple photos:
import SwiftUI
import PhotosUI
import CoreTransferable
struct ContentView: View {
#State var imageData: Data?
#State var selectedItems: [PhotosPickerItem] = []
var body: some View {
VStack {
if let imageData, let uiImage = UIImage(data: imageData) {
Image(uiImage: uiImage)
.resizable()
.scaledToFit()
.frame(width: 250, height: 250)
}
Spacer()
PhotosPicker(selection: $selectedItems,
matching: .images) {
Text("Pick Photo")
}
.onChange(of: selectedItems) { selectedItems in
if let selectedItem = selectedItems.first {
selectedItem.loadTransferable(type: Data.self) { result in
switch result {
case .success(let imageData):
if let imageData {
self.imageData = imageData
} else {
print("No supported content type found.")
}
case .failure(let error):
fatalError(error.localizedDescription)
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Selection of multiple photos is working totally fine and this code can preview first photo very cleanly. Is there any other way I can show selected multiple photos in SwiftUI?
You just need to change the imageData to be an array.
import SwiftUI
import PhotosUI
import CoreTransferable
#available(iOS 16.0, *)
struct MultipleSelectView: View {
#State var images: [UIImage] = []
#State var selectedItems: [PhotosPickerItem] = []
var body: some View {
VStack {
ForEach(images, id:\.cgImage){ image in
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(width: 250, height: 250)
}
Spacer()
PhotosPicker(selection: $selectedItems,
matching: .images) {
Text("Pick Photo")
}
.onChange(of: selectedItems) { selectedItems in
images = []
for item in selectedItems {
item.loadTransferable(type: Data.self) { result in
switch result {
case .success(let imageData):
if let imageData {
self.images.append(UIImage(data: imageData)!)
} else {
print("No supported content type found.")
}
case .failure(let error):
print(error)
}
}
}
}
}
}
}
#available(iOS 16.0, *)
struct MultipleSelectView_Previews: PreviewProvider {
static var previews: some View {
MultipleSelectView()
}
}

Refresh remotely loaded image in SwiftUI

It's a lot of code and looks daunting, but it's pretty simple--I'm trying to load remote image, and when the image is clicked, I'd like to switch to the next image:
struct TestView: View {
#State var selectedIndex: Int = 0
#State var arrayOfImages: [String] = ["https://s3-media3.fl.yelpcdn.com/bphoto/_0bkRz0wln3URHevWORCkA/o.jpg", "https://s3-media2.fl.yelpcdn.com/bphoto/MDZXc4pDt5xUfXF0Rw6rMw/o.jpg", "https://s3-media3.fl.yelpcdn.com/bphoto/feYg35an2MilNK3dCwwqTQ/o.jpg"]
var body: some View {
RemoteImage(url: arrayOfImages[selectedIndex])
.scaledToFill()
.frame(width: 200, height: 200)
.clipped()
.onTapGesture {
selectedIndex += 1
}
}
}
struct RemoteImage: View {
private enum LoadState {
case loading, success, failure
}
private class Loader: ObservableObject {
var data = Data()
var state = LoadState.loading
init(url: String) {
guard let parsedURL = URL(string: url) else {
fatalError("Invalid URL: \(url)")
}
URLSession.shared.dataTask(with: parsedURL) { data, response, error in
if let data = data, data.count > 0 {
self.data = data
self.state = .success
} else {
self.state = .failure
}
DispatchQueue.main.async {
self.objectWillChange.send()
}
}.resume()
}
}
#StateObject private var loader: Loader
var loading: Image
var failure: Image
var body: some View {
selectImage()
.resizable()
}
init(url: String, loading: Image = Image(""), failure: Image = Image(systemName: "multiply.circle")) {
_loader = StateObject(wrappedValue: Loader(url: url))
self.loading = loading
self.failure = failure
}
private func selectImage() -> Image {
switch loader.state {
case .loading:
return loading
case .failure:
return failure
default:
if let image = UIImage(data: loader.data) {
return Image(uiImage: image)
} else {
return failure
}
}
}
}
Here's the problem: the image doesn't go to the next one when you tap on it. I think it's because the RemoteImage view isn't being reloaded, but I'm not sure how to fix. Any help is appreciated!
I think you are trying to do too much inside RemoteImage, in particular
declaring private #StateObject private var loader: Loader and all that derives from this.
Try this approach with #StateObject var loader = Loader() outside your RemoteImage.
Works for me.
struct ContentView: View {
var body: some View {
TestView()
}
}
struct TestView: View {
#StateObject var loader = Loader() // <-- here
#State var selectedIndex: Int = 0
#State var arrayOfImages: [String] = ["https://s3-media3.fl.yelpcdn.com/bphoto/_0bkRz0wln3URHevWORCkA/o.jpg", "https://s3-media2.fl.yelpcdn.com/bphoto/MDZXc4pDt5xUfXF0Rw6rMw/o.jpg", "https://s3-media3.fl.yelpcdn.com/bphoto/feYg35an2MilNK3dCwwqTQ/o.jpg"]
var body: some View {
RemoteImage(loader: loader) // <--- here
.scaledToFill()
.frame(width: 200, height: 200)
.clipped()
.onTapGesture {
selectedIndex += 1
if selectedIndex < arrayOfImages.count {
loader.load(url: arrayOfImages[selectedIndex]) // <-- here
} else {
//...
}
}
.onAppear {
loader.load(url: arrayOfImages[selectedIndex]) // <--- here
}
}
}
class Loader: ObservableObject {
var data = Data()
var state = LoadState.loading
func load(url: String) { // <--- here
guard let parsedURL = URL(string: url) else {
fatalError("Invalid URL: \(url)")
}
URLSession.shared.dataTask(with: parsedURL) { data, response, error in
if let data = data, data.count > 0 {
self.data = data
self.state = .success
} else {
self.state = .failure
}
DispatchQueue.main.async {
self.objectWillChange.send()
}
}.resume()
}
}
enum LoadState {
case loading, success, failure
}
struct RemoteImage: View {
#ObservedObject var loader: Loader // <--- here
var loading: Image = Image("")
var failure: Image = Image(systemName: "multiply.circle")
var body: some View {
selectImage().resizable()
}
private func selectImage() -> Image {
switch loader.state {
case .loading:
return loading
case .failure:
return failure
default:
if let image = UIImage(data: loader.data) {
return Image(uiImage: image)
} else {
return failure
}
}
}
}

Folder URL from .onDrop() modifier

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

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

SwiftUI can't get image from download url

I have the following code to load an image from a download url and display it as an UIImage.
I expected it to work, but somehow, solely the placeholder image 'ccc' is being displayed, and not the actual image from the download url. How so?
My urls are being fetched from a database and kind of look like this:
https://firebasestorage.googleapis.com/v0/b/.../o/P...alt=media&token=...-579f...da
struct ShelterView: View {
var title: String
var background: String
var available: Bool
var distance: Double
var gender: String
#ObservedObject private var imageLoader: Loader
init(title: String, background: String, available: Bool, distance: Double, gender: String) {
self.title = title
self.background = background
self.available = available
self.distance = distance
self.gender = gender
self.imageLoader = Loader(background)
}
var image: UIImage? {
imageLoader.data.flatMap(UIImage.init)
}
var body: some View {
VStack {
VStack(alignment: .leading) {
VStack(alignment: .leading, spacing: 0) {
Text(title)
.font(Font.custom("Helvetica Now Display Bold", size: 30))
.foregroundColor(.white)
.padding(15)
.lineLimit(2)
HStack(spacing: 25) {
IconInfo(image: "bed.double.fill", text: String(available), color: .white)
if gender != "" {
IconInfo(image: "person.fill", text: gender, color: .white)
}
}
.padding(.leading, 15)
}
Spacer()
IconInfo(image: "mappin.circle.fill", text: String(distance) + " miles away", color: .white)
.padding(15)
}
Spacer()
}
.background(
Image(uiImage: image ?? UIImage(named: "ccc")!) <-- HERE
.brightness(-0.11)
.frame(width: 255, height: 360)
)
.frame(width: 255, height: 360)
.cornerRadius(30)
.shadow(color: Color("shadow"), radius: 10, x: 0, y: 10)
}
}
final class Loader: ObservableObject {
var task: URLSessionDataTask!
#Published var data: Data? = nil
init(_ urlString: String) {
print(urlString)
let url = URL(string: urlString)
task = URLSession.shared.dataTask(with: url!, completionHandler: { data, _, _ in
DispatchQueue.main.async {
self.data = data
}
})
task.resume()
}
deinit {
task.cancel()
}
}
Your image is a plain old var which happens to be nil when the View is built. SwiftUI only rebuilds itself in response to changes in #ObservedObject, #State, or #Binding, so move your image to an #Published property on your imageLoader and it will work. Here is my caching image View:
import SwiftUI
import Combine
import UIKit
class ImageCache {
enum Error: Swift.Error {
case dataConversionFailed
case sessionError(Swift.Error)
}
static let shared = ImageCache()
private let cache = NSCache<NSURL, UIImage>()
private init() { }
static func image(for url: URL?) -> AnyPublisher<UIImage?, ImageCache.Error> {
guard let url = url else {
return Empty().eraseToAnyPublisher()
}
guard let image = shared.cache.object(forKey: url as NSURL) else {
return URLSession
.shared
.dataTaskPublisher(for: url)
.tryMap { (tuple) -> UIImage in
let (data, _) = tuple
guard let image = UIImage(data: data) else {
throw Error.dataConversionFailed
}
shared.cache.setObject(image, forKey: url as NSURL)
return image
}
.mapError({ error in Error.sessionError(error) })
.eraseToAnyPublisher()
}
return Just(image)
.mapError({ _ in fatalError() })
.eraseToAnyPublisher()
}
}
class ImageModel: ObservableObject {
#Published var image: UIImage? = nil
var cacheSubscription: AnyCancellable?
init(url: URL?) {
cacheSubscription = ImageCache
.image(for: url)
.replaceError(with: nil)
.receive(on: RunLoop.main, options: .none)
.assign(to: \.image, on: self)
}
}
struct RemoteImage : View {
#ObservedObject var imageModel: ImageModel
private let contentMode: ContentMode
init(url: URL?, contentMode: ContentMode = .fit) {
imageModel = ImageModel(url: url)
self.contentMode = contentMode
}
var body: some View {
imageModel
.image
.map { Image(uiImage:$0).resizable().aspectRatio(contentMode: contentMode) }
?? Image(systemName: "questionmark").resizable().aspectRatio(contentMode: contentMode)
}
}