My app has an option to turn off some variables from CoreData and hide them from the user if they check that option. However, im running into an issue then when saving data to CoreData where the app crashes because there is no value put in for those entries. The issues are from my Double variables which are inserted via Textfield. All Strings and Ints I have working correctly. Obviously my Textfields all take in a String and in my function to save the data into CoreData it converts the string to a double. But it won't let me put ?? 0 next to the doubles like it does with INT. NOTE: All variables are optional in the entity.
Here is my function for saving to CoreData which works like a charm:
private func addItem() {
withAnimation {
let newItem = Item(context: viewContext)
let maskTypeString = String("\(maskSelect)")
let sleepPositionString = String("\(sleepSelect)")
let minPressureInt = Int16("\(minPressureString)")
let maxPressureInt = Int16("\(maxPressureString)")
let tubeTempInt = Int16("\(tubeTempString)")
guard let hoursSleptDouble = Double(hoursSleptString) else {
print("hours slept reading is invalid")
return
}
guard let ahiDouble = Double(ahiString) else {
print("ahi reading is invalid")
return
}
guard let leaksDouble = Double(leaksReadingString) else {
print("leaks reading is invalid")
return
}
guard let hoursMaskDouble = Double(hoursWithMaskString) else {
print("hours with mask reading is invalid")
return
}
newItem.id = UUID()
newItem.usedCPAP = usedCPAPToggle
newItem.ahiReading = ahiDouble
newItem.hoursSlept = hoursSleptDouble
newItem.feelingGood = feelingToggle
newItem.todaysDate = todaysDateInput
newItem.leaksReading = leaksDouble
newItem.humiditySetting = humiditySettingString
newItem.eprSetting = eprSettingString
newItem.sleepPosition = sleepPositionString
newItem.maskType = maskTypeString
newItem.headacheToggle = headacheToggle
newItem.hoursWithMask = hoursMaskDouble
newItem.alcoholDrinks = alcoholDrinksToggle
newItem.workoutFactor = workoutToggle
newItem.caffeineDrinks = caffeineDrinksToggle
newItem.sleepNotes = sleepNotes
newItem.minPressure = minPressureInt ?? 0
newItem.maxPressure = maxPressureInt ?? 0
newItem.ateLate = ateLateToggle
newItem.drugsTaken = drugsTakenToggle
newItem.eprRamp = eprRampToggle
newItem.tubeTemp = tubeTempInt ?? 0
newItem.napTaken = napTakenToggle
do {
try viewContext.save()
dismiss()
print("Success")
} catch {
print("CoreData save fail!!!")
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
Thanks to #Joakim Danielson above who solved this for me. I removed the guard/else statements and made them plain constants converting the strings to Doubles. And then when assigning the values to CoreData, used the ?? 0 which it now allowed.
let hoursSleptDouble = Double(hoursSleptString)
let ahiDouble = Double(ahiString)
let leaksDouble = Double(leaksReadingString)
let hoursMaskDouble = Double(hoursWithMaskString)
newItem.ahiReading = ahiDouble ?? 0
newItem.hoursSlept = hoursSleptDouble ?? 0
newItem.leaksReading = leaksDouble ?? 0
newItem.hoursWithMask = hoursMaskDouble ?? 0
Related
I need help with currency exchange rate lookup given a key (3 digit currency code). The JSON object is rather unusual with no lablels such as date, timestamp, success, or rate. The first string value is the base or home currency. In the example below it is "usd" (US dollars).
I would like to cycle through all the currencies to get each exchange rate by giving its 3 digit currency code and storing it in an ordered array.
{
"usd": {
"aed": 4.420217,
"afn": 93.3213,
"all": 123.104693,
"amd": 628.026474,
"ang": 2.159569,
"aoa": 791.552347,
"ars": 111.887966,
"aud": 1.558363,
"awg": 2.164862,
"azn": 2.045728,
"bam": 1.9541,
"bbd": 2.429065,
"bch": 0.001278
}
}
In a slightly different formatted JSON object I used the following loop to copy exchange rates to an ordered array.
for index in 0..<userData.rateArray.count {
currencyCode = currCode[index]
if let unwrapped = results.rates[currencyCode] {
userData.rateArray[index] = 1.0 / unwrapped
}
}
The follow code is the API used to get the 3 digit currency codes and the exchange rates (called via UpdateRates).
class GetCurrency: Codable {
let id = UUID()
var getCurrencies: [String : [String: Double]] = [:]
required public init(from decoder: Decoder) throws {
do{
print(#function)
let baseContainer = try decoder.singleValueContainer()
let base = try baseContainer.decode([String : [String: Double]].self)
for key in base.keys{
getCurrencies[key] = base[key]
}
}catch{
print(error)
throw error
}
}
}
class CurrencyViewModel: ObservableObject{
#Published var results: GetCurrency?
#Published var selectedBaseCurrency: String = "usd"
func UpdateRates() {
let baseUrl = "https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api#1/latest/currencies/"
let baseCur = selectedBaseCurrency // usd, eur, cad, etc
let requestType = ".json"
guard let url = URL(string: baseUrl + baseCur + requestType) else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
do{
let decodedResponse = try JSONDecoder().decode(GetCurrency.self, from: data)
DispatchQueue.main.async {
self.results = decodedResponse
// this prints out the complete table of currency code and exchange rates
print(self.results?.getCurrencies["usd"] ?? 0.0)
}
} catch {
//Error thrown by a try
print(error)//much more informative than error?.localizedDescription
}
}
if error != nil {
//data task error
print(error!)
}
}.resume()
}
}
Thanks lorem ipsum for your help. Below is the updated ASI logic that copies the exchange rates to the rateArray using key/value lookups.
class CurrencyViewModel: ObservableObject{
#Published var results: GetCurrency?
#Published var rateArray = [Double] ()
init() {
if UserDefaults.standard.array(forKey: "rates") != nil {
rateArray = UserDefaults.standard.array(forKey: "rates") as! [Double]
}else {
rateArray = [Double] (repeating: 0.0, count: 160)
UserDefaults.standard.set(self.rateArray, forKey: "rates")
}
}
func updateRates(baseCur: String) {
...
DispatchQueue.main.async {
self.results = decodedResponse
// loop through all available currencies
for index in 0..<currCode.count {
currencyCode = currCode[index]
// spacial handling for base currency
if currencyCode == baseCur {
self.rateArray[index] = 1.0000
} else {
let homeRate = self.results?.getCurrencies[baseCur]
// complement and save the exchange rate
if let unwrapped = homeRate?[currencyCode] {
self.rateArray[index] = 1.0 / unwrapped
}
}
}
}
} catch {
//Error thrown by a try
print(error)//much more informative than error?.localizedDescription
}
}
if error != nil {
//data task error
print(error!)
}
}.resume()
}
}
I've got a tableview showing some data and I filter the shown data uisng UISearchbar. Each data struct consists of different values and
struct Cake {
var name = String()
var size = String()
var filling = String()
}
When a user starts typing I don't know whether he is filtering for name, size or filling. I don't want to use a scopebar. Is there a way to filter for various fields at the same time in swift 3?
This is the code I use to filter:
func updateSearchResults(for searchController: UISearchController) {
if searchController.searchBar.text! == "" {
filteredCakes = cakes
} else {
// Filter the results
filteredCakes = cakes.filter { $0.name.lowercased().contains(searchController.searchBar.text!.lowercased()) }
}
self.tableView.reloadData()
}
thanks for your help!
func updateSearchResults(for searchController: UISearchController)
{
guard let searchedText = searchController.searchBar.text?.lowercased() else {return}
filteredCakes = cakes.filter
{
$0.name.lowercased().contains(searchedText) ||
$0.size.lowercased().contains(searchedText) ||
$0.filling.lowercased().contains(searchedText)
}
self.tableView.reloadData()
}
I am fetching user's information like his name,phone number and email id from contacts.But it is only showing first contact number.IF a person has more than one contact number,it didnt show that second number.Can someone help?I am using this function
where EVContactProtocol is part of Library
func didChooseContacts(_ contacts: [EVContactProtocol]?) {
var conlist : String = ""
if let cons = contacts {
for con in cons {
if let fullname = con.fullname(),let email1 = con.email , let phoneNumber = con.phone {
conlist += fullname + "\n"
print("Full Name: ",fullname)
print("Email: ",email1)
print("Phone Number: ",phoneNumber)
}
}
self.textView?.text = conlist
} else {
print("I got nothing")
}
let _ = self.navigationController?.popViewController(animated: true)
}
You should try this:
import Contacts
class ViewController: UIViewController
{
lazy var contacts: [CNContact] =
{
let contactStore = CNContactStore()
let keysToFetch = [
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
CNContactEmailAddressesKey,
CNContactPhoneNumbersKey] as [Any]
// Get all the containers
var allContainers: [CNContainer] = []
do
{
allContainers = try contactStore.containers(matching: nil)
}
catch
{
print("Error fetching containers")
}
var results: [CNContact] = []
// Iterate all containers and append their contacts to our results array
for container in allContainers
{
let fetchPredicate = CNContact.predicateForContactsInContainer(withIdentifier: container.identifier)
do
{
let containerResults = try contactStore.unifiedContacts(matching: fetchPredicate, keysToFetch: keysToFetch as! [CNKeyDescriptor])
results.append(contentsOf: containerResults)
}
catch
{
print("Error fetching results for container")
}
}
return results
}()
override func viewDidLoad()
{
super.viewDidLoad()
print(contacts[0].givenName)
print(contacts[0].phoneNumbers)
print(contacts[0].emailAddresses)
print(contacts)
}
}
I have a timetable app, and after converting everything to Swift 3, one particular line threw an EXC_BAD_INSTRUCTION error, stating "Unexpectedly found nil while unwrapping an Optional value"
Here is the code, the final line returns the error:
class CyclicDay {
enum CyclicDayError: Error {
case invalidStartDate }
lazy var baseline: Date! = {
var components = DateComponents()
components.day = 27
components.month = 3
components.year = 2017
return Calendar.current.date(from: components)!
}()
func dayOfCycle(_ testDate: Date) throws -> Int {
if let start = baseline {
let interval = testDate.timeIntervalSince(start as Date)
let days = interval / (60 * 60 * 24)
return Int(days.truncatingRemainder(dividingBy: 14)) + 1 }
throw CyclicDayError.invalidStartDate }}
override func viewDidLoad() {
// Do any additional setup after loading the view, typically from a nib.
let cd = CyclicDay()
let day = try! cd.dayOfCycle(Date())
let date = Date()
let calendar = Calendar.current
let components = calendar.dateComponents([.hour, .minute], from: date)
let hour = components.hour
let minutes = components.minute
_ = "\(String(describing: hour)):\(String(describing: minutes))"
let lengthTestHour = "\(String(describing: hour))"
let lengthTestMinute = "\(String(describing: minutes))"
let formatter = DateFormatter()
formatter.dateFormat = "a"
formatter.amSymbol = "AM"
formatter.pmSymbol = "PM"
let dateString = formatter.string(from: Date())
var finalHour = String()
if lengthTestHour.characters.count == 1 {
finalHour = String("0\(String(describing: hour))")
} else {
finalHour = "\(String(describing: hour))"
}
if lengthTestMinute.characters.count == 1 {
_ = "0\(String(describing: minutes))"
} else {_ = minutes }
let convert = finalHour
let mTime = Int(convert)
// mTime * 100 + minutes
let compTime = mTime! * 100 + minutes!
In Swift 3 all date components are optional, you need to unwrap the optionals
let hour = components.hour!
let minutes = components.minute!
otherwise you get in trouble with the string interpolations.
Btw: You don't need String(describing just write for example
_ = "\(hour):\(minutes)"
I'm wondering anyway why you do all the formatting stuff manually instead of using the date formatter you created.
The problem lies in these two lines:
let lengthTestHour = "\(String(describing: hour))"
let lengthTestMinute = "\(String(describing: minutes))"
You thought lengthTestHour will store a value like "7" and lengthTestMinute will have a value like "33". But no, lengthTestHours actually holds "Optional(7)" and lengthTestMinutes actually holds "Optional(33)".
You then assign lengthTestHour to convert and try to convert that Optional(7) thing into an Int, which obviously can't be done. Now mTime is nil and you try to force unwrap in the last line. BOOM!
This is because String(describing:) returns an optional. The two lines can be shortened and fixed by doing:
let lengthTestHour = "\(hour!)"
let lengthTestMinute = "\(minute!)"
I am having trouble storing coordinates into a variable. I set a variable to type string! and then when the function runs the latitude and longitude are both stored separately. I am also performing a segue because the sign-up process I am using requires multiple screens.
The data then gets stored into firebase. All of my other fields get uploaded to firebase but, the latitude and longitude do not show up at all.
Here is my code.
import UIKit
import CoreLocation
class SignUpLocation: UIViewController {
lazy var geocoder = CLGeocoder()
var latitude: String!
var longitude: String!
//Geocoding
private func processResponse(withPlacemarks placemarks: [CLPlacemark]?, error: Error?) {
if let error = error {
print("Unable to Forward Geocode Address (\(error))")
locationLabel.text = "Unable to Find Location for Address"
} else {
var location: CLLocation?
if let placemarks = placemarks, placemarks.count > 0 {
location = placemarks.first?.location
}
if let location = location {
let coordinate = location.coordinate
locationLabel.text = "\(coordinate.latitude), \(coordinate.longitude)"
latitude = "\(coordinate.latitude)"
longitude = "\(coordinate.longitude)"
} else {
locationLabel.text = "No Matching Location Found"
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let thirdVC = segue.destination as! SignUpContact
//:::: Initial Sign Up Values
thirdVC.firstNameVar2 = firstNameVar
thirdVC.businessNameVar2 = businessNameVar
thirdVC.emailVar2 = emailVar
thirdVC.passwordVar2 = passwordVar
//:::: Second Sign Up Values
thirdVC.streetAddressVar = streetAddressTextField.text!
thirdVC.cityVar = cityTextField.text!
thirdVC.countryVar = countryTextField.text!
thirdVC.stateVar = stateTextField.text!
thirdVC.zipcodeVar = zipcodeTextField.text!
thirdVC.latitudeVar = latitude
thirdVC.longitudeVar = longitude
}