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!)"
Related
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
I want to add local notification in my app. I am filling information in textfields and set date, time and reminder before particular date selected for the exam. Anyone implement such a demo then please suggest me what to do.
Answer is based on what ever i understood, Please change the time and reminder string as per your requirement.
func scheduleNotification(InputUser:String) {
let now: NSDateComponents = NSCalendar.currentCalendar().components([.Hour, .Minute], fromDate: NSDate())
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let date = cal.dateBySettingHour(now.hour, minute: now.minute + 1, second: 0, ofDate: NSDate(), options: NSCalendarOptions())
let reminder = UILocalNotification()
reminder.fireDate = date
reminder.alertBody = InputUser
reminder.alertAction = "Cool"
reminder.soundName = "sound.aif"
reminder.repeatInterval = NSCalendarUnit.Minute
UIApplication.sharedApplication().scheduleLocalNotification(reminder)
print("Firing at \(now.hour):\(now.minute+1)")
}
Set up Daily basic Local Notification 1 Day before or you can be modified it with the help of specific date in Swift 3.1
import UIKit
import UserNotifications
fileprivate struct AlarmKey{
static let startWorkIdentifier = "com.Reminder.Notification" //Add your own Identifier for Local Notification
static let startWork = "Ready for work? Toggle To \"Available\"."
}
class AlarmManager: NSObject{
static let sharedInstance = AlarmManager()
override init() {
super.init()
}
//MARK: - Clear All Previous Notifications
func clearAllNotifications(){
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
} else {
UIApplication.shared.cancelAllLocalNotifications()
}
}
func addReminder(with _ hour: Int, minutes: Int){
clearAllNotifications()
var dateComponent = DateComponents()
dateComponent.hour = hour // example - 7 Change you time here Progrmatically
dateComponent.minute = minutes // example - 00 Change you time here Progrmatically
if #available(iOS 10.0, *) {
dateComponent.timeZone = TimeZone.autoupdatingCurrent
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponent, repeats: false) //Set here **Repeat** condition
let content = UNMutableNotificationContent()
content.body = AlarmKey.startWork //Message Body
content.sound = UNNotificationSound.default()
let notification = UNNotificationRequest(identifier: AlarmKey.startWorkIdentifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().add(notification) {(error) in
if let error = error {
print("Uh oh! We had an error: \(error)")
}
}
} else {
//iOS *9.4 below if fails the Above Condition....
dateComponent.timeZone = NSTimeZone.system
let calender = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian)!
let date = calender.date(from: dateComponent)!
let localNotification = UILocalNotification()
localNotification.fireDate = date
localNotification.alertBody = AlarmKey.startWork
localNotification.repeatInterval = NSCalendar.Unit.day
localNotification.soundName = UILocalNotificationDefaultSoundName
UIApplication.shared.scheduleLocalNotification(localNotification)
}
}
}
//This is optional method if you want to show your Notification foreground condition
extension AlarmManager: UNUserNotificationCenterDelegate{
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Swift.Void){
completionHandler([.alert,.sound])
}
}
After converting my code to Swift 3.0, I have just one error left, saying that 'dateComponents' produces 'DateComponents', not the expected conextual result type 'NSDate?' I'll add a * on the line that error applies to. The * is not in the actual code.
I did not understand the code before hand, as it was written by a person I am no longer in touch with. The code sets a starting date for a timetabling app I wrote.
My code:
#IBOutlet weak var llllll: UILabel!
class CyclicDay {
enum CyclicDayError: Error {
case invalidStartDate }
lazy var baseline: NSDate? = {
var components = DateComponents()
components.day = 2
components.month = 5
components.year = 2016
* return NSCalendar.current.dateComponents(components) }()
func dayOfCycle(_ testDate: NSDate) 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 }}
A couple of issues:
In Swift 3, you should use Calendar and Date rather than NSCalendar and NSDate.
The method to get the Date object from a DateComponents is date(from:). The dateComponents method is for going the other direction, to determine the DateComponents from a Date.
Thus:
lazy var baseline: Date? = {
var components = DateComponents()
components.day = 2
components.month = 5
components.year = 2016
return Calendar.current.date(from: components)
}()
Or
lazy var baseline: Date = {
var components = DateComponents()
components.day = 2
components.month = 5
components.year = 2016
return Calendar.current.date(from: components)!
}()
Try using Calendar and Date rather than NSCalendar or NSDate. It may solve the problem. Most of 'NS' prefix are abondoned in swift 3.0
First of all, why is baseline optional? The date 2016/5/2 doubtless exists.
The Swift 3 equivalent is simply
lazy var baseline: Date = {
let components = DateComponents(year:2016, month:5, day:2)
return Calendar.current.date(from:components)!
}()
This is the code I used in Xcode 7.3.1 and that worked fine:
var selectedFiles = NSMutableArray(capacity:1)
let finder: AnyObject! = SBApplication(bundleIdentifier:"com.apple.finder")
let finderObject = finder.selection as! SBObject
let selection: AnyObject! = finderObject.get()
let items = selection.arrayByApplyingSelector(Selector("URL"))
let filteredfiles = (items as NSArray).pathsMatchingExtensions(["ai","pdf","ap","paf","pafsc"])
for item in filteredfiles {
let url = NSURL(string:item ,relativeToURL:nil)
selectedFiles.addObject(url!)
}
This is the code corrected for Xcode 8.0 and that does not work:
the error is generated for the last line
error = Cannot call value of non-function type '[Any]!'
var selectedFiles = NSMutableArray(capacity:1)
let finder: AnyObject! = SBApplication(bundleIdentifier:"com.apple.finder")
let finderObject = finder.selection as! SBObject
if let selection = finderObject.get() as AnyObject?{
let items = selection.array(#selector(getter: NSTextCheckingResult.url))
let filteredfiles = (items as NSArray).pathsMatchingExtensions(["ai","pdf","ap","paf","pafsc"])
for item in filteredfiles {
let url = NSURL(string:item ,relativeToURL:nil)
selectedFiles.addObject(url!)
}
}
I have tried many solutions, but unfortunately cannot find a clue.
I guess this is because Swift 3.0x APIs have changed drastically....
Any help is welcome!
This is a slightly different approach using a couple of native Swift functions for Swift 3
var selectedFiles = [URL]()
let finder : AnyObject = SBApplication(bundleIdentifier:"com.apple.finder")!
let finderObject = finder.selection as! SBObject
if let selection = finderObject.get() as? [SBObject] {
selection.forEach { item in
let url = URL(string: item.value(forKey:"URL") as! String)!
selectedFiles.append(url)
}
let goodExtensions = ["ai","pdf","ap","paf","pafsc"]
let filteredURLs = selectedFiles.filter({goodExtensions.contains($0.pathExtension)})
print(filteredURLs)
}
PS: I highly recommend to use AppleScriptObjC. It's so much easier to use.
PPS: valueForKey is intentionally used because KVC is really needed to get the property value.
I had this internal Date extension that appears to no longer work in Swift 3:
internal extension DateComponents {
func to12pm() {
self.hour = 12
self.minute = 0
self.second = 0
}
}
The error message is:
Cannot assign to property: 'self' is immutable
How do I achieve this in Swift 3?
Additional Info (if needed): It was called by this Date extension:
func endOfWeek(_ weekday: Int) -> Date? {
guard
let cal = Calendar.current,
var comp: DateComponents = (cal as Calendar).components([.weekOfYear], from: self)
else {
return nil
}
comp.weekOfYear = 1
comp.day -= 1
comp.to12pm()
return (cal as NSCalendar).date(byAdding: comp, to: self.startOfWeek(weekday)!, options: [])!
}
Which now looks like this in Swift 3:
func endOfWeek(_ weekday: Int) -> Date? {
let cal = Calendar.current
var comp = cal.dateComponents([.weekOfYear], from: self)
comp.weekOfYear = 1
comp.day? -= 1
//This does not have the "comp.to12pm()" that the Swift 2 version did
return cal.date(byAdding: comp, to: self.startOfWeek(weekday)!)!
}
DateComponents is now a struct thats why it is throwing that error:
Cannot assign to property: 'self' is immutable
You need to create a new var from self, change it and return it as follow:
extension DateComponents {
var to12pm: DateComponents {
var components = self
components.hour = 12
components.minute = 0
components.second = 0
components.nanosecond = 0
return components
}
}