How to Save an Array of Custom Objects All At Once to a Core Data Store in Swift - arrays

I have a very similar question to:
Swift: Store arrays of custom classes in Core Data
The difference is that question was looking to add to a custom class array in Core Data one element at a time. I am looking to add the entire array at once. When I try to, I get the following error:
[CDTester.MassOfSubpart entity]: unrecognized selector sent to instance.
I made a simple app to demonstrate the error and hopefully figure out where I am going wrong. I uploaded it to GitHub here: https://github.com/Yrban/Core-Data-Test-App
The code where it crashes is here:
func saveWidget(){
print("saveWidget() entered")
if self.massOfSubpartMeasurementDict.count > 0,
self.massOfSubpartMeasurementDict.count == self.numberOfSubparts,
self.manufacturer != "",
self.title != "" {
var massPerSubpartMeasurementArray: [MassOfSubpart]
massPerSubpartMeasurementArray = massOfSubpartMeasurementDict.map {
return MassOfSubpart(subpart: $0.key, mass: Measurement(value: Double($0.value)!, unit: self.massUnit))
}
let massOfSubpartSet = Set(massPerSubpartMeasurementArray)
let widget = Widget(context: self.managedObjectContext)
widget.id = UUID()
widget.madeBy?.name = self.manufacturer
widget.hasMassOfPart = massOfSubpartSet // FIXME: The crash is here
widget.title = self.title
do {
print("SaveWidget() attempted/n")
try self.managedObjectContext.save()
} catch {
print("SaveWidget() failed/n")
// handle the Core Data error
// Show alert as to status
}
}
}
My Core Data model is:
MassOfSubpart is a custom object:
import SwiftUI
public class MassOfSubpart: NSObject, Codable {
var subPart: Int
var mass: Measurement<UnitMass>
override init() {
self.subPart = 1
self.mass = Measurement(value: 0.0, unit: UnitMass.grams)
}
init(subpart: Int, mass: Measurement<UnitMass>){
self.subPart = subpart
self.mass = mass
}
}
I have read everything I could find, including Apple's documentation. I feel I am close, but I am obviously missing something important. Any help would be appreciated.

I finally figured out my error. I have not used relationships before, so it didn't dawn on me what I was doing. When I was accessing MassOfPartEntity through its relationship with Widget, I was sending a Set, my custom object through. However, I had to send a Set, the actual entity through. Therefore, I changed saveWidget to this:
func saveWidget(){
print("saveWidget() entered")
if self.massOfSubpartMeasurementDict.count > 0,
self.massOfSubpartMeasurementDict.count == self.numberOfSubparts,
self.manufacturer != "",
self.title != "" {
let massOfPartEntity = MassOfPartEntity(context: self.managedObjectContext)
var massPerSubpartEntityArray: [MassOfPartEntity]
massPerSubpartEntityArray = massOfSubpartMeasurementDict.map {
massOfPartEntity.subPart = Int16($0.key)
if let massDouble = Double($0.value) {
massOfPartEntity.mass = Measurement(value: massDouble, unit: massUnit)
} else {
massOfPartEntity.mass = Measurement(value: 0.0, unit: massUnit)
}
return massOfPartEntity
}
let massOfSubpartSet = Set(massPerSubpartEntityArray) as NSSet
let widget = Widget(context: self.managedObjectContext)
widget.id = UUID()
widget.madeBy?.name = self.manufacturer
widget.addToHasMassOfPart(massOfSubpartSet) // FIXME: The crash is here
widget.title = self.title
do {
print("SaveWidget() attempted/n")
try self.managedObjectContext.save()
} catch {
print("SaveWidget() failed/n")
// handle the Core Data error
// Show alert as to status
}
}
}
changing the Map function return so that it returned MassOfPartEntity's that eventually became a Set. Essentially, it was a type mismatch.
Thanks to those who responded as talking it out definitely helped resolve this.

Related

Weekly activity summary help? SWIFTUI

I'm tryin' to obtain a list of activities ("dd/mm/YY: goal achieved/missed goal") which has to be setted every week. The problem is that I obtain a list of activities with the same date and the same result of the previous one. For example:
28/02/2022: goal achieved
28/02/2022: goal achieved
28/02/2022: goal achieved
and the next day:
01/03/2022: missed goal
01/03/2022: missed goal
01/03/2022: missed goal
01/03/2022: missed goal
I want to obtain, instead, a list like:
28/02/2022: goal achieved
01/03/2022: missed goal
02/03/2022: goal achieved...
These are useful structs:
struct Persistent {
#AppStorage("goalAchieved") static var goalAchieved : Bool = false
#AppStorage("activityList") static var activityList : [String] = []
}
struct obj {
static var currentDate = Date()
static var stringDate = ""
static var activity = Activity(date:Persistent.lastUpdatedDate)
}
This is the ActivityListView:
import SwiftUI
func activitystring(activity:Activity) -> String{
var output = ""
output = "\(activity.date): \(activity.reachedobj(goalAchieved: Persistent.goalAchieved))"
return output
}
struct Activity: Identifiable{
let id = UUID()
let date: String
func reachedobj(goalAchieved: Bool) -> String {
var output = ""
if Persistent.goalAchieved == false { output = "Missed goal" }
if Persistent.goalAchieved == true { output = "Goal Achieved!"}
return output
}
}
struct ActivityRow: View{
var activity: Activity
var body: some View{
Text(activitystring(activity: activity))
Divider()
}
}
struct ActivityListView: View {
var body: some View {
ScrollView{
Text("Week summary").font(.system(size: 15)).foregroundColor(Color.green)
Text("")
ForEach(Persistent.activityList, id: \.self) { activity in
let activity = Activity(date: Persistent.lastUpdatedDate)
ActivityRow(activity: activity)
}
}
}
}
Finally this is the useful code in the ApplicationApp file (main) where I update activity list:
MenuView().onAppear(){
if Persistent.activityList.count>7{
Persistent.activityList.removeAll()
}
obj.currentDate = Date()
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/YY"
obj.stringDate = formatter.string(from:obj.currentDate)
if Persistent.lastUpdatedDate != obj.stringDate{
Persistent.goalAchieved = false
let activity = Activity(date: Persistent.lastUpdatedDate)
Persistent.activityList.append(activitystring(activity: activity))
Persistent.lastUpdatedDate = obj.stringDate
}
}
What's wrong on this?
You're calling obj.activity in your ForEach and ActivityRow, that's why it repeats that same static property all over the place.
You better just drop your struct obj and try again without it
In your Persistent object you have an array of many activities, called activitylist , but one single boolean that tells if the goal is achieved - goalachieved indeed.
Your view is iterating through the array of Persistent.activitylist, so you will have many lines for one single result - achieved or not achieved. You might actually want to iterate over an array of Persistent objects - meaning that somewhere you should probably store [Persistent] in some variable. In this way, you will see one line only for each result.
If I also may suggest: use the conventions for naming variables, Swift uses "camelCaseConventionForVariables", easier to read than "thewholevariableislowercase"
Edit:
Let me try to change a little bit your code (I would personally change it more radically, but that's not the scope of the answer).
Instead of having only one goalAchieved for all elements on the array activityList, make it a dictionary:
struct Persistent {
// Drop this variable
// #AppStorage("goalAchieved") static var goalAchieved : Bool = false
// Make this a dictionary, the date will be the key and the goalAchieved will be the value
#AppStorage("activityList") static var activityList : [String: Bool] = [:]
}
Add values to the dictionary (#meomeomeo is right, you don't need obj):
MenuView().onAppear() {
if Persistent.activityList.count > 7 {
Persistent.activityList.removeAll()
}
let currentDate = Date()
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/YY"
let stringDate = formatter.string(from: currentDate)
if Persistent.lastUpdatedDate != stringDate {
let activity = Activity(date: Persistent.lastUpdatedDate)
Persistent.activityList[activitystring(activity: activity))] = false // Will create something like ["01/03/2022": false]
Persistent.lastUpdatedDate = stringDate
}
}
Iterate on the dictionary in your ForEach; for more info: read here.

Speed Up a list

I'm try to speed up my APP that for now unfortunately is very slow on performing some search and list of data with Swift Ui.
first of all I have a data model that describe my object airport, called AirportModel
import Foundation
class AirportModel : Identifiable , Codable{
var aptICAO : String
var aptName : String
var aptCity : String
var aptCountry :String
var aptIATA : String
init(aptICAO: String, aptName: String, aptCity: String, aptCountry: String, aptIATA: String) {
self.aptICAO = aptICAO
self.aptName = aptName
self.aptCity = aptCity
self.aptCountry = aptCountry
self.aptIATA = aptIATA
}
}
I have a local file apt.json, that contain all the data information for my airport (I downloaded from internet this json, inside there are around 29000 airport)
so when I run the first time the app, with the following function , i create and save the airportVector of type AirportModel.
func openfilejson (fileName : String) {
if let path = Bundle.main.path(forResource: fileName, ofType: "json") {
do {
let fileUrl = URL(fileURLWithPath: path)
let datafile = try Data(contentsOf: fileUrl,options: .mappedIfSafe)
let json = JSON(data:datafile)
objectWillChange.send()
for (key,_) in json {
let airport = AirportModel(aptICAO: "", aptName: "", aptCity: "", aptCountry: "", aptIATA: "")
airport.aptName = json[key]["name"].stringValue
airport.aptCity = json[key]["city"].stringValue
airport.aptCountry = json[key]["country"].stringValue
airport.aptIATA = json[key]["iata"].stringValue
airport.aptICAO = json[key].stringValue
airportVector.append(airport)
}
debugPrint("nel vettore airport Vector ci sono \(airportVector.count) aeroporti")
save2() // to save airport vector in the plistfile
debugPrint("SALVATO IN MEMORIA ")
} catch {
print("ERRORE OPEN FILE AEROPORTI")
}
}
}
all, this work fine , the vector with 29000 airport inside is created once the app is ru the first time.
NOW THE BIG ISSUE.
in one view, I try to list this airport using SwiftUI and a searchBar to search on it.
the problem is, due to the big amount of data to be opened, when I load the View with the list; the data take very long to be listed and moreover when I lunch the search everything is stuck!
any idea how can I process or speed up this huge amount of data to avoid the app stuck on the loading of the following view!
import SwiftUI
struct ContentView: View {
#ObservedObject var dm: DataManager
#State private var searchTerm : String = ""
var body: some View {
VStack {
Text("List")
SearchBar(text: $searchTerm).shadow(radius: 10)
List(dm.airportVector.filter{
$0.aptCity.localizedCaseInsensitiveContains(searchTerm)
|| $0.aptName.localizedCaseInsensitiveContains(searchTerm)
}){ item in
HStack {
Text(item.aptICAO).bold().font(Font.system(size:20)).frame(width: 90, height: 10)
//
Text(item.aptName).font(Font.system(size: 15))
Spacer()
VStack(alignment: .leading) {
Text(item.aptCity).font(Font.system(size: 10)).font(.subheadline)
Spacer()
Text(item.aptCountry).font(Font.system(size: 10))
}
}
}
}
}
}
Thanks in advance.
Damiano
Make your data conform to Identifiable and use ForEach inside of your List with a separate View to render the item:
List {
ForEach(items) {
MyItem(item)
}
}
Only render items when they change by making MyItem conform to Equatable, and define the == function. Make sure the == function is being executed by adding a breakpoint or print statement. If it's not being executed see this article for a workaround:
https://swiftui-lab.com/equatableview/
If the above suggestion works, but you find yourself making wholesale modifications to the list after initial render, try using id() during these modifications. See https://swiftui-lab.com/swiftui-id/
If the above suggestions don't improve things enough, you probably need a more custom solution. Use a TableView or CollectionView, see https://github.com/apptekstudios/ASCollectionView

Updating element values of an array in swift

I have a lot of experience working with Matlab, but I only recently started programming in Swift 4. My current project involves building a questionnaire. I have used the ‘drag and drop’ feature in Xcode to produce an #IBAction function for a button in storyboard, which can then lead to pressed button changing its appearance. This functionality is contained within the ButtonResponse class in the code snippet below:
struct ResponseProfile {
var responseArray: Array<String>
init(responseArray: Array<String>) {
self.responseArray = ["unchecked","unchecked","unchecked","unchecked","unchecked","unchecked","unchecked"]
}
mutating func updateArray(_ anArray: Array<String>) -> (Array<String>) {
responseArray = anArray
return responseArray
}
}
class ButtonResponse: UIButton {
var responseVariables: ResponseProfile
var checkedImage = UIImage(named: "checkedResponseBox")! as UIImage
var uncheckedImage = UIImage(named: "uncheckedResponseBox")! as UIImage
required init?(coder aDecoder: NSCoder) {
self.responseVariables = ResponseProfile(
responseArray: []
)
super.init(coder: aDecoder)
}
#IBAction func checkboxTapped(_ sender: UIButton) {
switch sender.accessibilityIdentifier {
case "excellent":
let oldResponseStatus = responseVariables.responseArray[0]
if oldResponseStatus == "unchecked"{
sender.setImage(checkedImage, for: UIControlState.normal)
let oldResponsePresence = responseVariables.responseArray.contains("checked")
if oldResponsePresence == true {
responseVariables.responseArray = ["unchecked","unchecked","unchecked","unchecked","unchecked","unchecked","unchecked"]
}
responseVariables.responseArray[0] = "checked"
} else if oldResponseStatus == "checked" {
sender.setImage(uncheckedImage, for: UIControlState.normal)
responseVariables.responseArray[0] = "unchecked"
}
case "veryGood":
let oldResponseStatus = responseVariables.responseArray[1]
if oldResponseStatus == "unchecked" {
sender.setImage(checkedImage, for: UIControlState.normal)
let oldResponsePresence = responseVariables.responseArray.contains("checked")
if oldResponsePresence == true {
responseVariables.responseArray = ["unchecked","unchecked","unchecked","unchecked","unchecked","unchecked","unchecked"]
}
responseVariables.responseArray[1] = "checked"
} else if oldResponseStatus == "checked" {
sender.setImage(uncheckedImage, for: UIControlState.normal)
responseVariables.responseArray[1] = "unchecked"
}
default: break
}
}
}
I imagined that I could use an array to internally represent the state of the buttons in the user interface (this would be the ‘responseArray’ variable). By changing elements within responseArray following a button press, I thought I could keep track which buttons were pressed and ensure that no more than one button at a time was checked. I incorrectly thought responseArray would be updated, but this is not the case. The array always reverts to its initiation state.
N.B. responseArray contains seven elements because there are seven response options. So far, I have attempted to program only two of the response options: “excellent” and “veryGood”.
In attempting to find a solution, I attempted to simplify the above code in playground:
import UIKit
struct ResponseProfile {
var responseArray: Array<String>
init(responseArray: Array<String>) {
self.responseArray = ["unchecked","unchecked","unchecked","unchecked","unchecked","unchecked","unchecked"]
}
mutating func updateArray(input anArray: Array<String>) -> (Array<String>) {
responseArray = anArray
return responseArray
}
}
class ButtonResponse {
var responseVariables: ResponseProfile
init(){
self.responseVariables = ResponseProfile(responseArray: [])
}
var responseA = ResponseProfile(responseArray: [])
}
var responseOne = ResponseProfile(responseArray: [])
responseOne.responseArray[0] = "checked" //user performs action resulting in first element being changed from a starting value of "unchecked" to "checked"
responseOne.updateArray(input: responseOne.responseArray)
var responseTwo = ResponseProfile(responseArray:[])
responseTwo.responseArray //responseArray revert to initialization values. How can I keep changes to the responseArray?
How can I update responseArray within the ResponseProfile structure without having to create a new variable to record every change? Is this the problem I should be looking at or is there, on a more general level, a better strategy that I should be taking?
I am surprised that I struggled this much to deal with this issue. I thought the answer would be clear if I read the relevant parts of the documentation and studied some example code. All the example code I found was too simplistic and focused on just one iteration of updating the array.
Any comments or suggestions would be much appreciated!
Looking at your playground code, I found that you are passing a blank [] array to argument of ResponseProfile struct during init. and it is always initialising your responseArray.
If you want to pass the things by reference, you can change Response profile to class
and there you can achieve the similar functionalities and use inout parameter to keep the same array without using the function updateArray.
The example I am showing here is for the class and objects of class can be pass by reference. thus keep your previous changes.
var responseTwo = ResponseProfile(responseArray:[])
If you wants to keep the old response, you can pass that array as an argument
var responseTwo = ResponseProfile(responseArray:responseOne.responseArray)
OR
var responseTwo = responseOne
Will keep the responseArray.
You can read more about it, at official blog
Also you can this post with more insight for the case.
Hope it helps.
Thanks for your response Bhavin. By passing responseArray by reference (as Bhavin suggests) to the necessary class (which has turned out to be the ButtonResponse class rather than ResponseProfile), I can give responseArray an initial value. I then use the buttonPress function to update responseArray. See below:
class ButtonResponse: Responses {
var responseArray: [String]
init (responseArray: [String]) {
self.responseArray = responseArray
}
func buttonPress(inputString: String, targetIndex: Int) -> [String] {
//need guard statement to block function when targetIndex > number of elements in responseArray
responseArray[targetIndex] = inputString
return responseArray
}
}
let initiateResponseArray =
["unchecked","unchecked","unchecked","unchecked","unchecked","unchecked","unchecked"]
var doPress = ButtonResponse(responseArray: initiateResponseArray)
doPress.buttonPress(inputString: "checked", targetIndex: 0)
var getPressInfo1 = doPress.responseArray
print(getPressInfo1)
//prints: ["checked", "unchecked", "unchecked", "unchecked", "unchecked", "unchecked", "unchecked"]
doPress.buttonPress(inputString: "checked", targetIndex: 1)
var getPressInfo2 = doPress.responseArray
print(getPressInfo2)
//prints: ["checked", "checked", "unchecked", "unchecked", "unchecked", "unchecked", "unchecked"]
I am still unsure how to implement this solution in the project I am working on. I will create a separate question for this because it seems to raise different issues.

convert array of string into Double in swift

I'm trying to convert a string into a double in swift. I managed to extract the string from a website (www.x-rates.com) into an array but I cannot convert it after in a double in order to make some work around this number. Can anyone tell me what I'm supposed to do or what I did wrong? I know that my label don't update now but I will do it later, the first thing that I'm trying to do is the conversion.
thx a lot!
Here is the code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var resultLabel: UILabel!
#IBOutlet weak var moneyTextField: UITextField!
#IBAction func convert(_ sender: Any) {
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = URL(string: "https://www.x-rates.com/calculator/?from=EUR&to=USD&amount=1")!
let request = NSMutableURLRequest(url : url)
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
var message = ""
if let error = error {
print(error)
} else {
if let unwrappedData = data {
let dataString = NSString(data: unwrappedData, encoding: String.Encoding.utf8.rawValue)
var stringSeperator = "<span class=\"ccOutputRslt\">"
if let contentArray = dataString?.components(separatedBy: stringSeperator){
if contentArray.count > 0 {
stringSeperator = "<span"
let newContentArray = contentArray[1].components(separatedBy: stringSeperator)
if newContentArray.count > 0 {
message = newContentArray[0]
var message = Float(newContentArray[0])! + 10
}
}
}
}
}
DispatchQueue.main.sync(execute: {
self.resultLabel.text = "the value of the dollar is " + message
}
)}
task.resume()
func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I will talk about convert an Array of String to Array of Double.
In swift Array has a method called map, this is responsable to map the value from array, example, in map function you will receive an object referent to your array, this will convert this object to your new array ex.
let arrOfStrings = ["0.3", "0.4", "0.6"];
let arrOfDoubles = arrOfStrings.map { (value) -> Double in
return Double(value)!
}
The result will be
UPDATE:
#LeoDabus comments an important tip, this example is considering an perfect datasource, but if you have a dynamic source you can put ? on return and it will work, but this will return an array with nil
like that
let arrOfStrings = ["0.3", "0.4", "0.6", "a"];
let arrOfDoubles = arrOfStrings.map { (value) -> Double? in
return Double(value)
}
Look this, the return array has a nil element
If you use the tips from #LeoDabus you will protect this case, but you need understand what do you need in your problem to choose the better option between map or compactMap
example with compactMap
let arrOfStrings = ["0.3", "0.4", "0.6", "a"];
let arrOfDoubles = arrOfStrings.compactMap { (value) -> Double? in
return Double(value)
}
look the result
UPDATE:
After talk with the author (#davidandersson) of issue, this solution with map ou contactMap isn't his problem, I did a modification in his code and work nice.
first I replaced var message = "" per var rateValue:Double = 0.0 and replacedFloattoDouble`
look the final code
let url = URL(string: "https://www.x-rates.com/calculator/?from=EUR&to=USD&amount=1")!
let request = NSMutableURLRequest(url : url)
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
var rateValue:Double = 0.0;
if let error = error {
print(error)
} else {
if let unwrappedData = data {
let dataString = NSString(data: unwrappedData, encoding: String.Encoding.utf8.rawValue)
var stringSeperator = "<span class=\"ccOutputRslt\">"
if let contentArray = dataString?.components(separatedBy: stringSeperator){
if contentArray.count > 0 {
stringSeperator = "<span"
let newContentArray = contentArray[1].components(separatedBy: stringSeperator)
if newContentArray.count > 0 {
rateValue = Double(newContentArray[0])! + 10
}
}
}
}
}
//
print("Rate is \(rateValue)"); //Rate is 11.167
}
task.resume()
Hope to help you
The reason your code doesn’t work in my opinion is that you have two variables with the same name that are defined in different scopes and you use the wrong one at the end.
At the beginning you define
var message = ""
And then when converting to a number further down
var message = Float(newContentArray[0])! + 10
So change the last line to something like
var number = Float(newContentArray[0])! + 10
And use number in your calculations. Although I think
var number = Double(message)
should work equally fine since you have assigned newContentArray[0] to message already and Double is more commonly used than Float (I don’t understand + 10)

Swift: How to handle wait time for parse queries

I am making a Tinder like application where I query an array with parse and display the data in a deck of cards. I have a method where I query an array, and in the same method I use the returned list. However I get an error because the back card is called before the data is finished downloading. "fatal error: unexpectedly found nil while unwrapping an Optional value"
How can I handle this so that the code waits for the query to be retrieved?
Thanks
import UIKit
import MDCSwipeToChoose
class ChoosePersonViewController: UIViewController, MDCSwipeToChooseDelegate {
var people:[Person] = []
let ChoosePersonButtonHorizontalPadding:CGFloat = 80.0
let ChoosePersonButtonVerticalPadding:CGFloat = 20.0
var currentPerson:Person!
var frontCardView:ChoosePersonView!
var backCardView:ChoosePersonView!
let nameData = ""
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let userObjectID = PFUser.currentUser()!.objectId!
let query = PFQuery(className:"Restaurants")
query.whereKey("Pointer", equalTo: userObjectID)
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
print("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
if let objects = objects {
for object in objects {
var nameData = object.objectForKey("Name") as? String
var imageData = object.objectForKey("Image") as? PFFile
imageData!.getDataInBackgroundWithBlock { (imageData, error) -> Void in
if error == nil {
if let imageData = imageData
{
let image = UIImage(data: imageData)
self.people.append(Person(name: nameData, image: image, age: 21, sharedFriends: 3, sharedInterest: 4, photos: 5))
print(self.people)
}
}
}
}
}
} else {
// Log details of the failure
print("Error: \(error!) \(error!.userInfo)")
}
}
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
// Here you can init your properties
let imageData: PFFile
}
override func viewDidLoad(){
super.viewDidLoad()
// Display the first ChoosePersonView in front. Users can swipe to indicate
// whether they like or dislike the person displayed.
self.setMyFrontCardView(self.popPersonViewWithFrame(frontCardViewFrame())!)
self.view.addSubview(self.frontCardView)
// Display the second ChoosePersonView in back. This view controller uses
// the MDCSwipeToChooseDelegate protocol methods to update the front and
// back views after each user swipe.
self.backCardView = self.popPersonViewWithFrame(backCardViewFrame())!
self.view.insertSubview(self.backCardView, belowSubview: self.frontCardView)
// Add buttons to programmatically swipe the view left or right.
// See the `nopeFrontCardView` and `likeFrontCardView` methods.
constructNopeButton()
constructLikedButton()
}
func suportedInterfaceOrientations() -> UIInterfaceOrientationMask{
return UIInterfaceOrientationMask.Portrait
}
// This is called when a user didn't fully swipe left or right.
func viewDidCancelSwipe(view: UIView) -> Void{
print("You couldn't decide on \(self.currentPerson.Name)");
}
// This is called then a user swipes the view fully left or right.
func view(view: UIView, wasChosenWithDirection: MDCSwipeDirection) -> Void{
// MDCSwipeToChooseView shows "NOPE" on swipes to the left,
// and "LIKED" on swipes to the right.
if(wasChosenWithDirection == MDCSwipeDirection.Left){
print("You noped: \(self.currentPerson.Name)")
}
else{
print("You liked: \(self.currentPerson.Name)")
}
// MDCSwipeToChooseView removes the view from the view hierarchy
// after it is swiped (this behavior can be customized via the
// MDCSwipeOptions class). Since the front card view is gone, we
// move the back card to the front, and create a new back card.
if(self.backCardView != nil){
self.setMyFrontCardView(self.backCardView)
}
backCardView = self.popPersonViewWithFrame(self.backCardViewFrame())
//if(true){
// Fade the back card into view.
if(backCardView != nil){
self.backCardView.alpha = 0.0
self.view.insertSubview(self.backCardView, belowSubview: self.frontCardView)
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseInOut, animations: {
self.backCardView.alpha = 1.0
},completion:nil)
}
}
func setMyFrontCardView(frontCardView:ChoosePersonView) -> Void{
// Keep track of the person currently being chosen.
// Quick and dirty, just for the purposes of this sample app.
self.frontCardView = frontCardView
self.currentPerson = frontCardView.person
}
func popPersonViewWithFrame(frame:CGRect) -> ChoosePersonView?{
if(self.people.count == 0){
return nil;
}
// UIView+MDCSwipeToChoose and MDCSwipeToChooseView are heavily customizable.
// Each take an "options" argument. Here, we specify the view controller as
// a delegate, and provide a custom callback that moves the back card view
// based on how far the user has panned the front card view.
let options:MDCSwipeToChooseViewOptions = MDCSwipeToChooseViewOptions()
options.delegate = self
//options.threshold = 160.0
options.onPan = { state -> Void in
if(self.backCardView != nil){
let frame:CGRect = self.frontCardViewFrame()
self.backCardView.frame = CGRectMake(frame.origin.x, frame.origin.y-(state.thresholdRatio * 10.0), CGRectGetWidth(frame), CGRectGetHeight(frame))
}
}
// Create a personView with the top person in the people array, then pop
// that person off the stack.
let personView:ChoosePersonView = ChoosePersonView(frame: frame, person: self.people[0], options: options)
self.people.removeAtIndex(0)
return personView
}
I would suggest that starting all the image downloads in a hard loop is ineffective and likely to lead to network flooding and multiple request timeouts.
Consider just storing all of the returned objects and requesting only the first (and perhaps second) images.
Add the views immediately after saving the returned items and deal with adding the image to the view when it becomes available.
It seems that your issue might actually be related to trying to remove a view which doesn't exist yet, though it isn't clear what that function does and it might be something more like it wants to access an object which hasn't been added to the array yet because the next image hasn't downloaded so it can't find / create a suitable item to return... Changing the way you use images and views will resolve either of these causes.

Resources