How do I save an array of arrays to userDefaults? - ios

I'm trying to save an array of arrays of integers, which are used as a code to keep track of images. A user can upload images from the photo library and save them to different sections of the collection view (thus different sections of the array). When you open the app, you can only successfully save images to the first section (tops). When you refresh the app, only the photos from "tops" remain, nothing else. Additionally, after refreshing, I can no longer save images to userdefaults at all. Am I missing something?
var currentImageType = String()
// USER DEFAULTS
let userDefaults = UserDefaults.standard
var images: [[Int]] = userDefaults.object(forKey: "myKey") as? [[Int]] ?? [[0], [1000], [2000], [3000], [4000]]
class ViewController: UIViewController, PHPickerViewControllerDelegate {
#IBOutlet var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
}
// access photo library
#objc private func addPhotos(categoryType: String) {
var config = PHPickerConfiguration()
config.selectionLimit = 100
config.filter = .images
let vc = PHPickerViewController(configuration: config)
vc.delegate = self
present(vc, animated: true)
currentImageType = categoryType
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true, completion: nil)
let group = DispatchGroup()
results.forEach { result in
group.enter()
result.itemProvider.loadObject(ofClass: UIImage.self) { reading, error in
defer {
group.leave()
}
guard let image = reading as? UIImage, error == nil else {
return
}
// saving an image to database
func saveByType(Type: Int) {
// USER DEFAULTS
images[Type].append(images[Type].last! + 1)
print(images)
LocalFileManager.instance.saveImage(image: image, imageName: String(images[Type].last!), folderName: "closet")
userDefaults.set(images, forKey: "myKey")
}
switch currentImageType {
case "tops": saveByType(Type: 0)
case "outerwear": saveByType(Type: 1)
case "bottoms": saveByType(Type: 2)
case "singles": saveByType(Type: 3)
case "accessories": saveByType(Type: 4)
default: saveByType(Type: 0)
}
}
}
group.notify(queue: .main) {
self.collectionView.reloadData()
}
}
}
I followed a few examples online, and it looks like my code is in line with it (for the most part at least). Not sure what I'm missing, so any help would be appreciated!

Related

Improving the accuracy of text recognition when using iOS Vision Framework to scan a document

I am trying to build a document scanner that is able to read text off of any document/card. However, it sometimes has trouble identifying text correctly off of a credit card. The accuracy is decent, but there is definitely room for improvement. I used the VisionTextRecognition framework and have used all the standard settings which are the right ones for setting up text recognition.
This is what I had to setup the text recognition request
textRecognitionRequest = VNRecognizeTextRequest(completionHandler: { (request, error) in
if let results = request.results, !results.isEmpty {
if let requestResults = request.results as? [VNRecognizedTextObservation] {
var foundText = ""
for observation in recognizedText {
guard let candidate = observation.topCandidates(1).first else { continue }
foundText.append(candidate.string + "\n")
}
}
}
})
textRecognitionRequest.recognitionLevel = .accurate
textRecognitionRequest.usesLanguageCorrection = true
Does anyone have any suggestions for improving the identification programmatically by either pre-processing or post-processing the scan at some point?
UPDATE: I've made a fully open source project that may help you do exactly what you need. Check it out: https://github.com/ethanwa/credit-card-scanner-and-validator
**
You can't do much to improve accuracy beyond adding some preset values to specifically look for, which doesn't make sense with CC numbers so I won't even bother showing that code. You'll need to rely on Apple to improve their text recognition model as iOS iterates for it to truly improve.
What I suggest in the meantime are these two things you can do:
Do validation on your credit card numbers that you think you're recieving. For example, Visa starts with 4, MasterCard starts with 5, Discover with 6, Amex with 3, etc. They have specific lengths and so on. See here: https://www.freeformatter.com/credit-card-number-generator-validator.html
Keep iterating over and over on a camera feed until you get a number that validates. I'm not sure if you are currently just taking a picture of the card, and processing that image (which it sounds like you are doing), but you should be processing many images per second until you get a valid CC. This is most likely how Apple does it when adding a card via Apple Pay on your phone, or when depositing checks digitally using banking apps (finding valid routing and account numbers).
Here's an example of what I mean...
I wrote this code that can pick out and validate ISBN numbers (basically 10 and 13 digit numbers that catalog books, which have a check digit for validation) in any given text and will keep looking until it finds all the numbers and then validates. It works extremely well and is very fast. Check out this Swift 5.3 code:
import UIKit
import Vision
import Photos
import AVFoundation
class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {
var recognizedText = ""
var finalText = ""
var image: UIImage?
var processing = false
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var setLabel: UILabel!
#IBOutlet weak var numberLabel: UILabel!
lazy var textDetectionRequest: VNRecognizeTextRequest = {
let request = VNRecognizeTextRequest(completionHandler: self.handleDetectedText)
request.recognitionLevel = .accurate
request.usesLanguageCorrection = false
return request
}()
private let videoOutput = AVCaptureVideoDataOutput()
private let captureSession = AVCaptureSession()
private lazy var previewLayer: AVCaptureVideoPreviewLayer = {
let preview = AVCaptureVideoPreviewLayer(session: self.captureSession)
preview.videoGravity = .resizeAspect
return preview
}()
// MARK: AV
override func viewDidLoad() {
super.viewDidLoad()
self.addCameraInput()
self.addVideoOutput()
}
private func addCameraInput() {
let device = AVCaptureDevice.default(for: .video)!
let cameraInput = try! AVCaptureDeviceInput(device: device)
self.captureSession.addInput(cameraInput)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.previewLayer.frame = self.view.bounds
}
private func addVideoOutput() {
self.videoOutput.videoSettings = [(kCVPixelBufferPixelFormatTypeKey as NSString) : NSNumber(value: kCVPixelFormatType_32BGRA)] as [String : Any]
self.videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "my.image.handling.queue"))
self.captureSession.addOutput(self.videoOutput)
}
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection)
{
if !processing
{
guard let frame = CMSampleBufferGetImageBuffer(sampleBuffer) else {
debugPrint("unable to get image from sample buffer")
return
}
print("did receive image frame")
// process image here
self.processing = true
let ciimage : CIImage = CIImage(cvPixelBuffer: frame)
let theimage : UIImage = self.convert(cmage: ciimage)
self.image = theimage
processImage()
}
}
// Convert CIImage to CGImage
func convert(cmage:CIImage) -> UIImage
{
let context:CIContext = CIContext.init(options: nil)
let cgImage:CGImage = context.createCGImage(cmage, from: cmage.extent)!
let image:UIImage = UIImage.init(cgImage: cgImage)
return image
}
// AV
func processImage()
{
DispatchQueue.main.async {
self.nameLabel.text = ""
self.setLabel.text = ""
self.numberLabel.text = ""
}
guard let image = image, let cgImage = image.cgImage else { return }
let requests = [textDetectionRequest]
let imageRequestHandler = VNImageRequestHandler(cgImage: cgImage, orientation: .right, options: [:])
DispatchQueue.global(qos: .userInitiated).async {
do {
try imageRequestHandler.perform(requests)
} catch let error {
print("Error: \(error)")
}
}
}
fileprivate func handleDetectedText(request: VNRequest?, error: Error?)
{
self.finalText = ""
if let error = error {
print(error.localizedDescription)
self.processing = false
return
}
guard let results = request?.results, results.count > 0 else {
print("No text was found.")
self.processing = false
return
}
if let requestResults = request?.results as? [VNRecognizedTextObservation] {
self.recognizedText = ""
for observation in requestResults {
guard let candidiate = observation.topCandidates(1).first else { return }
self.recognizedText += candidiate.string
self.recognizedText += " "
}
var replaced = self.recognizedText.replacingOccurrences(of: "-", with: "")
replaced = String(replaced.filter { !"\n\t\r".contains($0) })
let replacedArr = replaced.components(separatedBy: " ")
for here in replacedArr
{
let final = here.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if (final.count == 10 || final.count == 13) && final.containsISBNnums && Validate.isbn(final) // validate barcode
{
self.finalText += final
print(final)
self.captureSession.stopRunning()
DispatchQueue.main.async {
self.previewLayer.removeFromSuperlayer()
}
break
}
}
DispatchQueue.main.async {
self.numberLabel.text = self.finalText
}
}
self.processing = false
}
// MARK: Buttons
// This is a live camera view that will start a capture session
#IBAction func takePhoto(_ sender: Any) {
self.view.layer.addSublayer(self.previewLayer)
self.captureSession.startRunning()
}
#IBAction func choosePhoto(_ sender: Any) {
presentPhotoPicker(type: .photoLibrary)
}
fileprivate func presentPhotoPicker(type: UIImagePickerController.SourceType) {
let controller = UIImagePickerController()
controller.sourceType = type
controller.delegate = self
present(controller, animated: true, completion: nil)
}
}
extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
dismiss(animated: true, completion: nil)
image = info[.originalImage] as? UIImage
processImage()
}
}
extension String {
var containsISBNnums: Bool {
guard self.count > 0 else { return false }
let nums: Set<Character> = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "X"]
return Set(self).isSubset(of: nums)
}
}

How to perform different function on the base of different type of segue?

I hope I would be able to explain my question.
I have a ViewController named TouristPictureViewController
class TouristPictureViewController: UIViewController {
#IBOutlet weak var picture: UIImageView!
var userID = Auth.auth().currentUser?.uid
var imageURL:String?
override func viewDidLoad() {
super.viewDidLoad()
getProfilePicture(SuccessCompletion: { (Img) in
self.picture.image = Img
}) { (error) in
print(error)
}
}
func getProfilePicture(SuccessCompletion: #escaping (_ image:UIImage) -> (), FailureCompletion: #escaping (_ error:Error) -> ()) {
let referecne = Storage.storage().reference(withPath: "user/\(userID!)")
referecne.getData(maxSize: 5 * 1024 * 1024) { (data, error) in
if let err = error {
print(err)
FailureCompletion(err)
}
else {
if let _data = data {
let myImage:UIImage! = UIImage(data: _data)
SuccessCompletion(myImage)
}
}
}
}
}
This VC can be called in two different conditions. First, it is being called when User want to see their profile picture and in that case, It is triggered through Storyboard.instantiateViewController and then in this VC, getProfilePicture function is called. Now, this VC is also triggered through segue, and in that case I am passing an Image URL to this VC and hence with that I want to show that particular Image in this VC. Now what condition should I put here so that the getProfile function should not be executed in viewDidLoad when I approach this VC by performing Segue?
Just check imageURL, it's nil in the instantiateViewController case
override func viewDidLoad() {
super.viewDidLoad()
if let urlString = imageURL {
// load image and assign it to self.picture.image
} else {
getProfilePicture(SuccessCompletion: { img in
self.picture.image = img
}) { error in
print(error)
}
}
}
You can declare a global boolean variable like shouldLoadProfile
in the TouristPictureViewController
var shouldLoadProfile: bool = false
And in the viewDidLoad() method, check shouldLoadProfile or not. Also, you need to set true or false into shouldLoadProfile variable before you transition to TouristPictureViewController.
override func viewDidLoad(){
super.viewDidLoad()
if shouldLoadProfile{
getProfilePicture(SuccessCompletion: { (Img) in
self.picture.image = Img
}) { (error) in
print(error)
}
}else{
//set imageURL into the UIImageView
}
}

Observe call from Firebase not updating value of dictionary after function exit

I'm trying to learn iOS programming so I thought it would be a good idea to emulate instagrams feed. Everyone uses this basic feed and I would like to know how to do it.
The basic idea is to have one image/text post show up in a single column. Right now I have a a single image to be shown.
I'm currently extracting the image url correctly from firebase. The only issue is that my CollectionView still is showing up empty. I started this project months ago and I forget where the tutorial is at. Please help me fill in the blanks. Here is the code:
import UIKit
import SwiftUI
import Firebase
import FirebaseUI
import SwiftKeychainWrapper
class FeedViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource{
#IBOutlet weak var collectionview: UICollectionView!
//var posts = [Post]()
var posts = [String](){
didSet{
collectionview.reloadData()
}
}
var following = [String]()
var posts1 = [String]()
var userStorage: StorageReference!
var ref : DatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
posts1 = fetchPosts()
//let myIndexPath = IndexPath(row: 0, section: 0)
//collectionView(collectionview, cellForItemAt: myIndexPath)
//print(self.posts1.count)
}
func fetchPosts() -> [String]{
let uid = Auth.auth().currentUser!.uid
let ref = Database.database().reference().child("posts")
let uids = Database.database().reference().child("users")
uids.observe(DataEventType.value, with: { (snapshot) in
let dict = snapshot.value as! [String:NSDictionary]
for (_,value) in dict {
if let uid = value["uid"] as? String{
self.following.append(uid)
}
}
ref.observe(DataEventType.value, with: { (snapshot2) in
let dict2 = snapshot2.value as! [String:NSDictionary]
for(key, value) in dict{
for uid2 in self.following{
if (uid2 == key){
for (key2,value2) in value as! [String:String]{
//print(key2 + "this is key2")
if(key2 == "urlToImage"){
let urlimage = value2
//print(urlimage)
self.posts1.append(urlimage)
self.collectionview.reloadData()
print(self.posts1.count)
}
}
}
}
}
})
})
//ref.removeAllObservers()
//uids.removeAllObservers()
print("before return")
print(self.posts1.count)
return self.posts1
override func viewDidLayoutSubviews() {
collectionview.reloadData()
}
func numberOfSections(in collectionView: UICollectionView) ->Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return posts1.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PostCell", for: indexPath) as! PostCell
cell.postImage.sd_setImage(with: URL(string: posts1[indexPath.row]))
//creating the cell
//cell.postImage.downloadImage(from: self.posts[indexPath.row])
// let storageRef = Storage.storage().reference(forURL: self.posts[indexPath.row].pathToImage)
//
//
print("im trying")
//let stickitinme = URL(fileURLWithPath: posts1[0])
//cell.postImage.sd_setImage(with: stickitinme)
//cell.authorLabel.text = self.posts[indexPath.row].author
//cell.likeLabel.text = "\(self.posts[indexPath.row].likes) Likes"
return cell
}
#IBAction func signOutPressed(_sender: Any){
signOut()
self.performSegue(withIdentifier: "toSignIn", sender: nil)
}
#objc func signOut(){
KeychainWrapper.standard.removeObject(forKey:"uid")
do{
try Auth.auth().signOut()
} catch let signOutError as NSError{
print("Error signing out: %#", signOutError)
}
dismiss(animated: true, completion: nil)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
UPDATE
The observe call is not updating the value of posts (the dictionary). Once the observe call exits, the value of posts is set back to empty.
PostCell class as asked:
import UIKit
class PostCell: UICollectionViewCell {
#IBOutlet weak var postImage: UIImageView!
#IBOutlet weak var authorLabel: UILabel!
#IBOutlet weak var likeLabel:UILabel!
#IBOutlet weak var likeBtn:UIButton!
#IBOutlet weak var unlikeBtn:UIButton!
#IBAction func likePressed (_ sender: Any){
}
#IBAction func unlikePressed(_sender: Any){
}
}
I think the problem is:
Your collectionView dataSource is called only once. Since the image url loading is asynchronous, you will need to refresh your collectionview every time new data is appended to your datasource array like this:
self.posts.append(urlimage)
collectionView.reloadData()
or:
var posts = [UIImage](){
didSet{
collectionView.reloadData()
}
}
Hope this helps.
Edit update:
Regarding the asynchronous calls, i think you should use escaping closure that runs the code block once the network request receives a response.
First separate the network call functions like:
func fetchUsers(completion: #escaping(_ dictionary: [String: NSDictionary])->()){
let uid = Auth.auth().currentUser!.uid
let uids = Database.database().reference().child("users")
uids.observe(DataEventType.value, with: { (snapshot) in
let dict = snapshot.value as! [String:NSDictionary]
completion(dict)
})
}
func fetchURLS(completion: #escaping(_ dictionary: [String: String])->()){
let ref = Database.database().reference().child("posts")
ref.observe(DataEventType.value, with: { (snapshot2) in
let dict2 = snapshot2.value as! [String:String]
completionTwo(dict2)
})
}
Then, the parsing functions:
func parseUsers(dictionary: [String: NSDictionary]){
for (_,value) in dictionary {
if let uid = value["uid"] as? String{
self.following.append(uid)
}
}
fetchURLS { (urlDictionary) in
self.parseImageURLS(dictionary: urlDictionary)
}
}
func parseImageURLS(dictionary: [String: String]){
for(key, value) in dictionary{
for uid2 in self.following{
if (uid2 == key){
for (key2,value2) in value as! [String:String]{
//print(key2 + "this is key2")
if(key2 == "urlToImage"){
let urlimage = value2
//print(urlimage)
self.posts1.append(urlimage)
self.collectionview.reloadData()
print(self.posts1.count)
}
}
}
}
}
}
Then you add:
fetchUsers { (usersDictionary) in
self.parseUsers(dictionary: usersDictionary)
}
in viewDidLoad()
Hope this solves your problem. On a side note: I recommend using models and separating the network calls in a different file. Feel free to ask any questions.
I figured out how to do it after more searching.
I was incorrectly assuming that the CollectionView is loaded after the viewDidLoad() function is done. The helper classes for a CollectionView are called to a call of reloadData.
I observed that my reloadData call wasn't being called. In order to make this work, I add 2 lines of code to the viewDidLoad function:
collectionview.delegate = self
collectionview.dataSource = self
With this change, the images now load.

How to save the order of a struct?

I created an app which randomly displays different Strings from an array. To make sure that no array gets repeated I created a struct which only shows the Strings which havenĀ“t already been displayed.
That works pretty well, but my problem is that as soon as I switch scenes the struct gets reset although I tried to save it to user defaults.
Does someone know where I made a mistake when I tried to apply UserDefaults?
Here is my struct containing the saving methods:
struct RandomItems: Codable
{
var items : [String]
var seen = 0
init(items:[String], seen: Int)
{
self.items = items
self.seen = seen
}
init(_ items:[String])
{ self.init(items: items, seen: 0) }
mutating func next() -> String
{
let index = Int(arc4random_uniform(UInt32(items.count - seen)))
let item = items.remove(at:index)
items.append(item)
seen = (seen + 1) % items.count
return item
}
func toPropertyList() -> [String: Any] {
return [
"items": items,
"seen": seen]
}
}
override func viewDidLoad() {
let defaults = UserDefaults.standard
if let quotes = defaults.codable(RandomItems.self, forKey: "quotes") as? RandomItems {
self.quotes = quotes
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector:Selector(("saveData:")), name: Notification.Name.UIApplicationWillTerminate, object:nil)
}
func storeQuotes() {
// Code to save struct
let defaults = UserDefaults.standard
if let quotes = quotes {
defaults.set(codable: quotes, forKey: "quotes")
}
}
override func viewDidAppear(_ animated: Bool) {
// Code to load the struct again after the view appears.
}
override func viewWillDisappear(_ animated: Bool) {
storeQuotes()
}
func saveData(notification: Notification) {
// Save your data here when app is closed
print("Saving data...")
storeQuotes()
}
}
extension UserDefaults {
func set<T: Encodable>(codable: T, forKey key: String) {
let encoder = JSONEncoder()
do {
let data = try encoder.encode(codable)
let jsonString = String(data: data, encoding: .utf8)!
print("Saving \"\(key)\": \(jsonString)")
self.set(jsonString, forKey: key)
} catch {
print("Saving \"\(key)\" failed: \(error)")
}
}
func codable<T: Decodable>(_ codable: T.Type, forKey key: String) -> T? {
guard let jsonString = self.string(forKey: key) else { return nil }
guard let data = jsonString.data(using: .utf8) else { return nil }
let decoder = JSONDecoder()
print("Loading \"\(key)\": \(jsonString)")
return try? decoder.decode(codable, from: data)
}
You can check in your viewDidLoad if you already have a value stored in UserDefaults, and you can remove your code in viewDidAppear:
override func viewDidLoad() {
let defaults = UserDefaults.standard
if let quotes = defaults.codable(RandomItems.self, forKey: "quotes") as? RandomItems {
self.quotes = quotes
}
}
EDIT:
And if you want to store your quotes when your app moves to background:
Add the notification observer in viewWillAppear:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(saveData), name: Notification.Name.UIApplicationDidEnterBackground, object:nil)
}
func storeQuotes() {
// Code to save struct
let defaults = UserDefaults.standard
if let quotes = quotes {
defaults.set(codable: quotes, forKey: "quotes")
}
}
override func viewWillDisappear(_ animated: Bool) {
// Code to save struct before the view disappears.
storeQuotes()
}
func saveData() {
// Save your data here when app is closed
print("Saving data...")
storeQuotes()
}

Passing data from a class to same instance of a viewcontroller

When a specific event happens(in my case when a tab bar is changed) I want to create a new link from an Array. I have gotten this to work but the problem I am not facing is when i try to pass the generated link to the same viewcontroller i get an error
fatal error: unexpectedly found nil while unwrapping an Optional value
This happens when I try to change the UILabel movietitle and imageview. I think this is because every time it sends the link it creates a new ViewController instead of using the existing one. Might also be that i have missed an unwrapped value somewhere. Hope someone here can help me!
StringBuilder:
import UIKit
class StringBuilder: NSObject {
let urlString = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acb9e5534ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=12"
let urlStringMultipleGenres = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acbf5534ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=28,12,10749"
var currentGenreArray: Array<Int> = []
//This will be run after the user has selected or deselected genres in the genreControllerView
func updateGenres(genreArrayIn: Array<Int>){
print("update genres input: ")
print(genreArrayIn)
//If new array input is the same as old arrayinput, do nothing
if genreArrayIn == currentGenreArray{
return
}
else{
let returnedLink = generateString(genreID: genreArrayIn)
print("Returned link after generate string" + returnedLink)
sendLink(link: returnedLink)
}
}
//After the updated genres have been put into an Array, this function will generate the whole string which
//will be the main String the getMovieRequest follows
func generateString(genreID: Array<Int>) -> String{
let filteredGenreArray = filterZeroes(unfilteredArray: genreID)
currentGenreArray = genreID
print("current genre array: ")
print(currentGenreArray)
let baseString = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acbfed4ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres="
var generatedString = baseString
for id in filteredGenreArray{
let k = String(id)
generatedString += k + ","
}
print("Generated Link from Strinbuilder: ")
print(generatedString)
return generatedString
}
func filterZeroes(unfilteredArray: Array<Int>) -> Array<Int>{
let filteredGenreArray = unfilteredArray.filter {$0 > 0}
print("filtered array: ")
print(filteredGenreArray)
return filteredGenreArray
}
func sendLink(link: String){
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let movieVC = storyBoard.instantiateViewController(withIdentifier: "movieView") as! ViewController
movieVC.getMovieData(activeGenreLink: link)
print("new link sent from sendlink()")
}
}
ViewController:
import UIKit
import Alamofire
import AlamofireImage
class ViewController: UIViewController{
static let sharedInstance = ViewController()
var movieIndex = 0
var movieArray:[Movie] = []
var downloadGrp = DispatchGroup()
#IBOutlet var uiMovieTitle: UILabel!
#IBOutlet var uiMoviePoster: UIImageView!
#IBOutlet var posterLoading: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let firstTimeLink = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acb9e5534ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=35,18"
getMovieData(activeGenreLink: firstTimeLink)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped(tapGestureRecognizer:)))
uiMoviePoster.isUserInteractionEnabled = true
uiMoviePoster.addGestureRecognizer(tapGestureRecognizer)
print("settings Sucessful")
}
func imageTapped(tapGestureRecognizer: UITapGestureRecognizer){
performSegue(withIdentifier: "detailsSegue", sender: self)
}
#IBAction func yesBtn(_ sender: UIButton) {
movieIndex += 1
updateUI()
}
#IBAction func seenBtn(_ sender: UIButton) {
movieIndex += 1
}
#IBAction func noBtn(_ sender: UIButton) {
movieIndex += 1
}
//Get movie data
func getMovieData(activeGenreLink: String){
//self.posterLoading.startAnimating()
movieIndex = 0
self.downloadGrp.enter()
Alamofire.request(activeGenreLink).responseJSON { response in
//print(response.request) // original URL request
//print(response.response) // HTTP URL response
//print(response.data) // server data
//print(response.result) // result of response serialization
self.movieArray = []
print(self.movieArray)
if let json = response.result.value as? Dictionary<String,AnyObject> {
if let movies = json["results"] as? [AnyObject]{
for movie in movies{
let movieObject: Movie = Movie()
let title = movie["title"] as! String
let releaseDate = movie["release_date"] as! String
let posterPath = movie["poster_path"] as! String
let overView = movie["overview"] as! String
let movieId = movie["id"] as! Int
let genre_ids = movie["genre_ids"] as! [AnyObject]
movieObject.title = title
movieObject.movieRelease = releaseDate
movieObject.posterPath = posterPath
movieObject.overView = overView
movieObject.movieId = movieId
for genre in genre_ids{//Genre ids, fix this
movieObject.movieGenre.append(genre as! Int)
}
Alamofire.request("http://image.tmdb.org/t/p/w1920" + posterPath).responseImage {
response in
//print(response.request)
//print(response.response)
//debugPrint(response.result)
if var image = response.result.value {
image = UIImage(data: response.data!)!
movieObject.poster = image
}
}
self.movieArray.append(movieObject)
}//End of for each movie
}
else{
print("error while making results anyobject")
}
}
else{
print("error while trying to make NSDictionary")}
self.downloadGrp.leave()
}//End of Json request
downloadGrp.notify( queue: .main){
print("all downloads finished")
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
print(self.movieArray[0].title!)
self.updateUI()
print("updatedUI")
}
}
}//End of getmoviedata
override func prepare(for segue: UIStoryboardSegue,sender: Any?){
// Create a variable that you want to send
let currentMovie = self.movieArray[movieIndex]
if let destinationVC = segue.destination as? DetailsViewController{
destinationVC.currentMovie = currentMovie
}
}
func updateUI(){
//self.posterLoading.stopAnimating()
if uiMoviePoster == nil{
print(uiMovieTitle.debugDescription)
}
else{
print("first time debugID: " + uiMovieTitle.debugDescription)
uiMovieTitle.text = self.movieArray[movieIndex].title
uiMoviePoster.image = self.movieArray[movieIndex].poster
}
}
}
You want to grab a sharedInstance not instantiate from a storyboard
let movieVC = ViewController.sharedInstance()
but I still do not understand why do you need to do it like this

Resources