How can I use firebase storage to download images in a file and show them in a table view? - swift

Good afternoon,
I have been stuck on this problem for months. I am trying to use firebase storage to save image files that a user uploaded. The program should then be able to update the queue and show the image in a horizontal table view. Kinda like netflix where its titles of movies/shows but mine would just be pictures. After trying to figure this out, this is what I came up with. Here is to receive the images
class ImageRecieve : ObservableObject {
#Published var songImageArrayURL = [URL]()
#Published var data : Data?
#Published var songImage : NSImage?
#Published var AlbumCoverArray = [NSImage]()
func GetURLS(){
//we want to get the download urls
bfRef.listAll { (result, error) in
if let error = error{ //if theres an error, print it
print(error.localizedDescription)
}
let prefixes = result.prefixes
//loop to search each song prefix
for i in prefixes.indices{
//get the song of each prefix
prefixes[i].listAll { (result, error) in
if let error = error {
print(error.localizedDescription)
}
else {
let items = result.items
//if anything contains ".mp3" dont add it to array.
for j in items.indices{
if(!items[j].name.contains("mp3")){
SongImage.append(items[j])
self.download(SongImage: items[j])
}
}
}
}
}
}
}
func download(SongImage:StorageReference){
//get download url
DispatchQueue.main.async {
SongImage.downloadURL { (url, error) in
if let error = error { //if there is an error print it
print(error.localizedDescription)
}
else {
if(url != nil){
self.songImage = NSImage(byReferencing: url!)
self.AlbumCoverArray.append(self.songImage!)
}
}
}
}
}
func load(){
if(self.songImageArrayURL.isEmpty){
GetURLS()
}
print(self.songImageArrayURL)
for i in self.songImageArrayURL.indices{
print(self.songImageArrayURL[i])
DispatchQueue.global().async{
if let data = try? Data(contentsOf: self.songImageArrayURL[i]){
if let image = NSImage(data:data){
DispatchQueue.main.async {
self.songImage = image
}
}
}
}
}
}
func cancel(){
}
}
here is to load the images :
struct LoadImages<Placeholder: View>: View {
#ObservedObject var loader : ImageRecieve
private var placeholder : Placeholder?
init(placeholder: Placeholder? = nil) {
loader = ImageRecieve()
self.placeholder = placeholder
}
var body: some View {
image
.onAppear(perform: loader.GetURLS)
.onDisappear(perform: loader.cancel)
}
private var image: some View{
ForEach(loader.AlbumCoverArray.indices,id:\.self){
i in
Group{
if(self.loader.songImage != nil){
Image(nsImage:self.loader.AlbumCoverArray[i]).resizable().frame(width:50, height:50)
}
else{
self.placeholder
}
}
}
}
}
the problem I've been stuck on is that the photos are only downloading one at a time and not listing one by one. For example, they show one image and then switch to the next. I would like an array of images. So that the images get added to the list. I've tried using an image array but it doesnt work.

photos are only downloading one at a time and not listing one by one.
in all languages an array/list is processed sequentially, you might want to use multi-Threading for parallelism. use a queue and assign few threads which download image, after each download pop the element from queue.
all the child threads append/push the data to the main thread. in that manner you will be able to display images as they load.
PS:i am != swiftie but seeing your programming i sense turmoil. try improving your code grammar and avoid too many functions and spaces.

Related

Can't fill my collection views with API data by using Alamofire

There is an api (https://docs.api.jikan.moe/#section/Information). I get data from it, but I can’t display them in my collection views in any way. The data should come, I checked. I implement filling the collection view cells through the view model ViewController <-> ViewModel and with Network Manager API Manager
The result is just white collectionView - Screen
For the first time I decided to work with Alamofire and apparently I don’t understand something. Please tell me what is the problem. Link to github in case someone needs it.
Updated
The problem might be with asynchronous coding. And i still have no ideas to fix it, cause don't understand the GCD as well. Screen
func fetchRequest(typeRequest: TypeRequest) -> [AnimeModel] {
var animeModels: [AnimeModel] = []
switch typeRequest {
case .name(let name):
let urlString = "https://api.jikan.moe/v4/anime?q=\(name)"
AF.request(urlString).response { response in
guard let data = response.data else { return print("NO DATA FOR - \(name)") }
do {
let json = try JSON(data: data)
let title = json["data"][0]["title_english"].string ?? "Anime"
let imageURL = json["data"][0]["images"]["jpg"]["image_url"].string ?? ""
let image = AnimeModel.downloadImage(stringURL: imageURL)
animeModels.append(AnimeModel(image: image, title: title))
print(".NAME ANIME MODELS - \(animeModels)")
} catch let error {
print(error.localizedDescription)
}
}
}
print("BEFORE RETURN ANIME MODELS - \(animeModels)")
return animeModels // returns empty array and then "animeModel.append()" is applied
}

implementation of NSMetadataQuery along with UIDocuments in swiftUI

I am trying to make a document based app in swiftUI with a custom UI. I want iCloud capabilities in my app. I am trying to use iCloud Document (No cloudKit) way for storing data on iCloud container. I am using UIDocument and it's working. It's storing data to iCloud and I am able to retrieve it back.
Now the thing is when I run the app on two devices (iphone and iPad) and make changes to a file on one device, the changes are not reflecting on the other device while the file or say app is open. I have to close the app and relaunch it to see the changes.
I know I have to implement NSMetadataQuery to achieve this but I am struggling with it. I don't know any objective-C. I have been searching on the internet for a good article but could not find any. Can you please tell how do I implement this feature in my app. I have attach the working code of UIDocument and my Model class.
Thank you in advance !
UIDocument
class NoteDocument: UIDocument {
var notes = [Note]()
override func load(fromContents contents: Any, ofType typeName: String?) throws {
if let contents = contents as? Data {
if let arr = try? PropertyListDecoder().decode([Note].self, from: contents) {
self.notes = arr
return
}
}
//if we get here, there was some kind of problem
throw NSError(domain: "NoDataDomain", code: -1, userInfo: nil)
}
override func contents(forType typeName: String) throws -> Any {
if let data = try? PropertyListEncoder().encode(self.notes) {
return data
}
//if we get here, there was some kind of problem
throw NSError(domain: "NoDataDomain", code: -2, userInfo: nil)
}
}
Model
class Model: ObservableObject {
var document: NoteDocument?
var documentURL: URL?
init() {
let fm = FileManager.default
let driveURL = fm.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents")
documentURL = driveURL?.appendingPathComponent("savefile.txt")
document = NoteDocument(fileURL: documentURL!)
}
func loadData(viewModel: ViewModel) {
let fm = FileManager.default
if fm.fileExists(atPath: (documentURL?.path)!) {
document?.open(completionHandler: { (success: Bool) -> Void in
if success {
viewModel.notes = self.document?.notes ?? [Note]()
print("File load successfull")
} else {
print("File load failed")
}
})
} else {
document?.save(to: documentURL!, for: .forCreating, completionHandler: { (success: Bool) -> Void in
if success {
print("File create successfull")
} else {
print("File create failed")
}
})
}
}
func saveData(_ notes: [Note]) {
document!.notes = notes
document?.save(to: documentURL!, for: .forOverwriting, completionHandler: { (success: Bool) -> Void in
if success {
print("File save successfull")
} else {
print("File save failed")
}
})
}
func autoSave(_ notes: [Note]) {
document!.notes = notes
document?.updateChangeCount(.done)
}
}
Note
class Note: Identifiable, Codable {
var id = UUID()
var title = ""
var text = ""
}
This is a complex topic. Apple do provide some sample swift code, the Document-Based App Programming Guide for iOS and iCloud Design Guide.
There is also some good third party guidance: Mastering the iCloud Document Store.
I would recommend reading the above, and then return to the NSMetaDataQuery API. NSMetaDataQuery has an initial gathering phase and a live-update phase. The later phase can remain in operation for the lifetime of your app, allowing you to be notified of new documents in your app's iCloud container.

How to upload multiple image on firebase using Swift's PHPickerController [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
so in Swift, you have the ability to upload an image/video with ease using UIImageViewController. I did research and came across PHPickerController and I am trying to incorporate that into my code - for the reasoning being that I want multiple images/videos selected and once user presses "button" it pushes that batch to firebase cloud. I have been struggling with this for sometime now. Any sample swift file of doing just this would be much appreciated.
This worked for me.
Note: Make sure that the images you are selecting from the photoLibrary are not the default ones that come with xCode. Some of the default images do not work because they don't have a file location.
SwiftUI Solution
Here is how you call the PHPickerViewController:
struct PHPicker: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> PHPickerViewController {
var config = PHPickerConfiguration()
config.selectionLimit = 5
config.filter = PHPickerFilter.images
let pickerViewController = PHPickerViewController(configuration: config)
pickerViewController.delegate = context.coordinator
return pickerViewController
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
}
class something: NSObject, PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
var fileName: Int = 5
for result in results {
// Get all the images that you selected from the PHPickerViewController
result.itemProvider.loadObject(ofClass: UIImage.self) { object, error in
// Check for errors
if let error = error {
print("Sick error dawg \(error.localizedDescription)")
} else {
// Convert the image into Data so we can upload to firebase
if let image = object as? UIImage {
let imageData = image.jpegData(compressionQuality: 1.0)
// You NEED to make sure you somehow change the name of each picture that you upload which is why I am using the variable "count".
// If you do not change the filename for each picture you upload, it will try to upload the file to the same file and it will give you an error.
Storage.storage().reference().child("fileName").child("\(fileName)").putData(imageData!)
fileName += 1
print("Uploaded to firebase")
} else {
print("There was an error.")
}
}
}
}
}
}
func makeCoordinator() -> something {
return something()
}
}
Here is how I present the sheet:
struct PresentMyPicker: View {
#State var presentSheet: Bool = false
var body: some View {
VStack {
Button {
presentSheet.toggle()
} label: {
Text("Click me")
}
}
.sheet(isPresented: $presentSheet) {
PHPicker()
}
}
}
UIKit solution
This is how I present the PHPickerViewController when they tap the button:
func setupView() {
var config = PHPickerConfiguration()
config.selectionLimit = 5
config.filter = PHPickerFilter.images
let pickerViewController = PHPickerViewController(configuration: config)
pickerViewController.delegate = self
view.addSubview(button)
button.addAction(UIAction() { _ in
self.present(pickerViewController, animated: true)
}, for: .touchUpInside)
}
Here is my delegate function that runs after you click "Add" with the selected images you want to upload.
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
var fileName: Int = 1
for result in results {
// Get all the images that you selected from the PHPickerViewController
result.itemProvider.loadObject(ofClass: UIImage.self) { object, error in
// Check for errors
if let error = error {
print("Sick error dawg \(error.localizedDescription)")
} else {
// Convert the image into Data so we can upload to firebase
if let image = object as? UIImage {
let imageData = image.jpegData(compressionQuality: 1.0)
// You NEED to make sure you somehow change the name of each picture that you upload which is why I am using the variable "fileName".
// If you do not change the filename for each picture you upload, it will try to upload all the selected images to the same file location and give you an error.
Storage.storage().reference().child("CollectionName").child("\(fileName)").putData(imageData!)
fileName += 1
} else {
print("There was an error.")
}
}
}
}
}
Also if you are wanting to upload videos to firebase and having trouble take a look at this example it took me forever to figure this out. Uploading Videos to firebase correctly.

How to play sound from one button based off of UIimageview image

i've developed a fully working flash card app for kids. It has a UIImageView that cycles thru 26 cards (abcs) via a gesture click and a music button that will play a sound for each image. Right now i have this 100% working BUT the sound plays in an IF statement that has added an additional 400 lines of code.
An example of the music button:
if (imageView.image?.isEqual(UIImage(named: "card1")))! {
do { audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: aSound))
audioPlayer.play()
} catch {
print("Couldn't load sound file")
}
}
else if (imageView.image?.isEqual(UIImage(named: "card2")))! {
do { audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: bSound))
audioPlayer.play()
} catch {
print("Couldn't load sound file")
}
}
else if (imageView.image?.isEqual(UIImage(named: "card3")))! {
do { audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: cSound))
audioPlayer.play()
} catch {
print("Couldn't load sound file")
}
}
I know i could set a sound array with the sound files in it, but how would i tie it back to the card? I cant set a tag property on an asset image can i?
Looking for a way to shorten the current code i have.
here is a way I would go about. Haven't tested it but it should get you there.! I use arrays, but Set or Dictionary would work.
1) Create a struct
struct Card {
let soundURL: URL
let image: UIImage
}
2) gather all the cards in an Array:
let cards: [Card] = {
var collection = [Card]()
let soundPaths: [String] = ["aSound", "bSound", "xSound", "zSound"]
let cardNames: [String] = ["card1", "card2", "card25", "card26"]
// array of all 26 urls
var soundUrls: [URL] {
var urls = [URL]()
for path in soundPaths {
urls.append(URL(fileURLWithPath: path))
}
return urls
}
// array of all 26 images
var cardImages: [UIImage] {
var images = [UIImage]()
for name in cardNames {
guard let image = UIImage(contentsOfFile: name) else { continue }
images.append(image)
}
return images
}
// not the best approach but if you have sound and naming
// in the order then:
for idx in 0..<26 {
let card = Card(soundURL: soundUrls[idx], image: cardImages[idx])
collection.append(card)
}
return collection
}()
3) add an extension to UIImage to play the sound:
extension UIImage {
func playSound() {
let card = cards.filter { $0.image == self }.first
if card != nil {
do {
let audioPlayer = try AVAudioPlayer(contentsOf: card!.soundURL)
audioPlayer.play()
} catch {
print("Couldn't load the sound file with error: \(error)")
}
}
}
}
Then when you get your imageView.image, you should be able to just do:
imageView.image?.playSound()
Again I didn't test this, but I think the logic still stand. Hope it can help.
This approach is completely wrong. Your image view is a view. You should not make any choices based on what it displays. You should make your choice based on something about your data model.
For example, let's say you have three images, and an array of three image names:
let images = ["Manny", "Moe", "Jack"]
var currentImageIndex = 0
And let's say you change the image index and display that image:
self.currentImageIndex = 1
self.imageView.image = UIImage(named:images[currentImageIndex])
Then the way you know which image is displayed is by looking at currentImageIndex — not by looking at the image view.
In real life, your model may be much more extensive, for example a struct and an array of structs. But the principle remains exactly the same. It is the model that tells you what to do, never the interface.

Load data before views in Swift

I'm trying to load some data via JSON from the web and save it globally to my app.
I have a separate swift file
struct MyAppData {
static var vendorCol = "15"
static var vendorDel = "45"
static var VendorID = "1"
}
Using alomofire i populate it.
Alamofire.request("https://domainname/data.json").responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let json = JSON(responseData.result.value!)
guard let times = json["content"]["clients"].array else {
print("No Data")
return
}
for time in times {
MyAppData.vendorCol = time["col"].string!
MyAppData.vendorDel = time["del"].string!
MyAppData.VendorID = time["id"].string!
}
}
}
This works as expected if I place it on a view controller and use MyAppData.VendorID
My issue is I need a place load the data at a place where it's immediately available to all views and only want to load it once.
I tried in app delegate with no success, the function was called but the data was never updated all over the app. Some labels I set only had the default values from MyAppData.
Any help would be appreciated.