iosswift4xcode10.1

'*** -[__NSSingleObjectArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'


After upgrade from swift3 to swift4, xcode9 to xcode10.1, App stop working when tapped on one button (it worked fine in swift3 and xcode9) And give warning:

    reason: '*** -[__NSSingleObjectArrayI objectAtIndex:]: 
index 1 beyond bounds [0 .. 0]'

and warning:

libc++abi.dylib: terminating with uncaught exception of type NSException

I googled and read these answers: 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 1 beyond bounds [0 .. 0]'

'NSRangeException', reason: '* -[__NSArrayM objectAtIndex:]: index 2 beyond bounds [0 .. 1]'

iOS error: [__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]

App crashing "libc++abi.dylib: terminating with uncaught exception of type NSException (lldb)"

https://github.com/invertase/react-native-firebase/issues/313

But I cannot understand the answers and what causing the problem. I also tried reconnect my button to the code, but not working.I need more precise where is the bug and how to fix.

What I got: Before tap on the button, the logger already given:

Failed to set (placeholderSpacing) user defined inspected property on 
(SkyFloatingLabelTextField.SkyFloatingLabelTextField):
[<SkyFloatingLabelTextField.SkyFloatingLabelTextField 0x10684f200> 
setValue:forUndefinedKey:]: this class is not key value coding-
compliant for the key placeholderSpacing.

After tap on the button, the app stop working and I got no error(red), but this in the debugger:

*** Terminating app due to uncaught exception 'NSRangeException', 
reason: '*** -[__NSSingleObjectArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'

*** First throw call stack:
(0x1..330ec4 .... 0x1d..7abb4)
libc++abi.dylib: terminating with uncaught exception of type NSException

The code stopped running at this line in AppDelegate.swift:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

Some other information in debugger shows issue in this line of XCGLogger.swift:

/// Option: a closure to execute whenever a logging method is called without a log message

open var noMessageClosure: () -> Any? = { return "" }

After tried the answer in:

https://stackoverflow.com/questions/36856150/nsrangeexception-reason-nsarraym-objectatindex-index-1-beyond-bo

I got some return log related to XCGLogger. Please see the image attached. Debug Image

Here's the button I tapped:

btnCheckIn

enter image description here

Here's the code of the button activity:

@IBAction func btnCheckInTapped(_ sender: UIButton) {
    if let vc = UIStoryboard.bookingPeriodVC() as? BookingPeriodVC{
        vc.delegate = self
        vc.modalTransitionStyle = .crossDissolve
        vc.modalPresentationStyle = .overCurrentContext
        tabBarController?.present(vc, animated: true, completion: nil)
    }
}

Storyboard name: EAN Scene

Code of BookingPeriodVC.swift:

import UIKit
import FSCalendar
import Device

@objc protocol SkyCalendarDelegate: class {
    @objc optional func didSelectDates(dates:[String])
    @objc optional func didSelectDateAndTime(dateTime: [String])
}

class BookingPeriodVC: UIViewController {

    fileprivate let currentCalendar = Calendar.current

    @objc weak var delegate: SkyCalendarDelegate?

    fileprivate var startSelectingDate: Bool = false {
        didSet {
            if calendar.getUserSelectedDate.count >= 2 {
                startDateValueLbl.text = calendar.getUserSelectedDate[0]
                endDateValueLbl.text = calendar.getUserSelectedDate[1]
            } else {
                if let firstDate = calendar.getUserSelectedDate.first {
                    startDateValueLbl.text = firstDate
                }else {
                    startDateValueLbl.text = ""
                }
                endDateValueLbl.text = ""
            }
            updateConstraintForDateLabels()
        }
    }

    fileprivate var currentSelectedDates:[Date] = [] // Temp var
    fileprivate var lastSelectedDate: Date = Date()

    @IBOutlet weak var containerView: UIView!
    @IBOutlet weak var startDateValueLbl: UILabel!
    @IBOutlet weak var endDateValueLbl: UILabel!


    @IBOutlet weak var calendarContainerView: UIView!
    @IBOutlet weak var previousBtn: UIButton!
    @IBOutlet weak var nextBtn: UIButton!

    @IBOutlet weak var calendarHeight: NSLayoutConstraint! {
        didSet {
            calendarHeight.constant = Device.size() == .screen3_5Inch ? 250 : 280
        }
    }

    @IBOutlet weak var startDateTopConstraint: NSLayoutConstraint!
    @IBOutlet weak var endDateTopConstraint: NSLayoutConstraint!


    @objc lazy var calendar: FSCalendar = {
        let calender = FSCalendar()
        calender.translatesAutoresizingMaskIntoConstraints = false
        calender.scope = .month
        calender.delegate = self
        calender.dataSource = self
        calender.swipeToChooseGesture.isEnabled = true

        calender.allowsMultipleSelection = true

        // First Row - DEC 2017
        calender.appearance.headerTitleFont = FontBook.Bold.of(size: 14)
        calender.appearance.headerTitleColor = Color.Tuna.instance()
        calender.appearance.headerMinimumDissolvedAlpha = 0
        calender.appearance.headerDateFormat = "MMMM yyyy"

        // Second Row
        calender.appearance.weekdayTextColor = Color.heather.instance()
        calender.appearance.weekdayFont = FontBook.Bold.of(size: 9)

        // day text color
        calender.appearance.titleDefaultColor = Color.Tuna.instance()
        calender.appearance.titleFont = FontBook.Bold.of(size: 14)

        // Uppercase text for first and second row
        calender.appearance.caseOptions = [.headerUsesUpperCase,.weekdayUsesUpperCase]
        calender.today = nil

        calender.register(SkyCalendarCell.self, forCellReuseIdentifier: "cell")

        calender.headerHeight = 50
        //calender.pagingEnabled = true
        calender.scrollEnabled = false

        calender.hero.isEnabled = true
        //calender.heroID = "calendar"
        calender.backgroundColor = .clear

        calender.subviews[1].backgroundColor = .white

        return calender
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        commonInit()
    }


    @objc func commonInit() {
        setupCalendar()
        startDateValueLbl.text = ""
        endDateValueLbl.text = ""
    }

    @IBAction func didPressDoneBtn(_ sender: UIButton) {

        if calendar.getUserSelectedDate.count >= 2 {
            delegate?.didSelectDates?(dates: calendar.getUserSelectedDate)
            dismiss(animated: true, completion: nil)
            return
        }
        print("Select start and end date")

    }

    @IBAction func didPressPrevNextCalendarBtn(_ sender : UIButton) {
        let tag = sender.tag
        doTimeTravel(tag)
    }

    @IBAction func didPressBackBtn(_ sender: UIButton) {
        self.dismiss(animated: true, completion: nil)
    }

    @IBAction func didPressClearBtn(_ sender: UIButton) {
        if calendar.selectedDates.count > 0 {
            calendar.selectedDates.forEach { calendar.deselect($0) }
            configureVisibleCells()
            startSelectingDate = false
            didSetDayScope = false
            calendar.reloadData()
        }
    }

    // is called when user tap on previous / next button of calendar
    @objc func doTimeTravel(_ tag: Int){
        calendar.scrollEnabled = true

        tag == 0 ? setPreviousAndNextForCurrentCalendarScope(value: -1) : setPreviousAndNextForCurrentCalendarScope(value: 1)

        configureVisibleCells()
        disablePanOnCalendar()
    }

    private func setupCalendar() {
        calendarContainerView.addSubview(calendar)

        calendar.topAnchor.constraint(equalTo: calendarContainerView.topAnchor).isActive = true
        calendar.leftAnchor.constraint(equalTo: calendarContainerView.leftAnchor).isActive = true
        calendar.rightAnchor.constraint(equalTo: calendarContainerView.rightAnchor).isActive = true
        calendar.bottomAnchor.constraint(equalTo: calendarContainerView.bottomAnchor).isActive = true

        previousBtn.leftAnchor.constraint(equalTo: calendar.leftAnchor, constant: 25).isActive = true
        previousBtn.centerYAnchor.constraint(equalTo: calendar.calendarHeaderView.centerYAnchor).isActive = true
        nextBtn.rightAnchor.constraint(equalTo: calendar.rightAnchor, constant: -25).isActive = true
        nextBtn.centerYAnchor.constraint(equalTo: calendar.calendarHeaderView.centerYAnchor).isActive = true

        calendarContainerView.bringSubview(toFront: nextBtn)
        calendarContainerView.bringSubview(toFront: previousBtn)

    }

    @objc func updateConstraintForDateLabels() {
        startDateTopConstraint.constant = 10
        endDateTopConstraint.constant = 10

        startDateValueLbl.isHidden = false
        endDateValueLbl.isHidden = false

        UIView.animate(withDuration: 0.4) {
            self.view.layoutIfNeeded()
        }
    }

    @objc var didSetDayScope = false
}

extension BookingPeriodVC: FSCalendarDataSource  {

    func calendar(_ calendar: FSCalendar, cellFor date: Date, at position: FSCalendarMonthPosition) -> FSCalendarCell {
        let cell = calendar.dequeueReusableCell(withIdentifier: "cell", for: date, at: position)
        return cell
    }

    func calendar(_ calendar: FSCalendar, willDisplay cell: FSCalendarCell, for date: Date, at position: FSCalendarMonthPosition) {
        self.configure(cell: cell, for: date, at: position)
    }

    func minimumDate(for calendar: FSCalendar) -> Date {
        if startSelectingDate {
            return lastSelectedDate
        }
        return Date()
    }


    func maximumDate(for calendar: FSCalendar) -> Date {
        let cldr = Calendar.current

        if startSelectingDate {
            return cldr.date(byAdding: .day, value: 28, to: calendar.selectedDate!) ?? Date()
        }
        return cldr.date(byAdding: .day, value: 499, to: Date()) ?? Date()
    }



    func calendar(_ calendar: FSCalendar, shouldSelect date: Date, at monthPosition: FSCalendarMonthPosition)   -> Bool {
        return monthPosition == .current
    }

    func calendar(_ calendar: FSCalendar, shouldDeselect date: Date, at monthPosition: FSCalendarMonthPosition) -> Bool {
        return monthPosition == .current
    }
}


/*
 * Responsible For drawing circle for selected dates
 */
extension BookingPeriodVC {

    @objc func configureVisibleCells() {
        calendar.visibleCells().forEach { (cell) in
            let date = calendar.date(for: cell)
            let position = calendar.monthPosition(for: cell)
            self.configure(cell: cell, for: date!, at: position)
        }
    }

    // marking cell when user select or drag on day
    @objc func configure(cell: FSCalendarCell, for date: Date, at position: FSCalendarMonthPosition) {

        let skyCell = cell as! SkyCalendarCell

        if position == .current {

            var selectionType = SelectionType.none

            if calendar.selectedDates.contains(date) {
                let previousDate = self.currentCalendar.date(byAdding: .day, value: -1, to: date)!
                let nextDate = self.currentCalendar.date(byAdding: .day, value: 1, to: date)!
                if calendar.selectedDates.contains(date) {
                    if calendar.selectedDates.contains(previousDate) && calendar.selectedDates.contains(nextDate) {
                        selectionType = .middle
                    }
                    else if calendar.selectedDates.contains(previousDate) && calendar.selectedDates.contains(date) {
                        selectionType = .rightBorder
                    }
                    else if calendar.selectedDates.contains(nextDate) {
                        selectionType = .leftBorder
                    }
                    else {
                        selectionType = .single
                    }
                }
            }else {
                selectionType = .none
            }

            if selectionType == .none {
                skyCell.selectionLayer.isHidden = true
                return
            }
            skyCell.selectionLayer.isHidden = false
            skyCell.selectionType = selectionType

        } else {
            skyCell.selectionLayer.isHidden = true

        }
    }
}

extension BookingPeriodVC: FSCalendarDelegate {

    func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
        self.configureVisibleCells()

        if calendar.selectedDates.count > 1 {
            selectAllDateBetween(start: lastSelectedDate, end: date)
        }

        lastSelectedDate = date
        startSelectingDate = true

        if startSelectingDate && !didSetDayScope {
            calendar.reloadData()
            didSetDayScope = true
        }
    }

    func calendar(_ calendar: FSCalendar, didDeselect date: Date) {
        deselectDateStartingFrom(date: date, selectedDates: calendar.selectedDates)
        lastSelectedDate = currentCalendar.date(byAdding: .day, value: -1, to: date) ?? date
        configureVisibleCells()
        startSelectingDate = false

        if calendar.selectedDates.count < 1 {
            didSetDayScope = false
            calendar.reloadData()
        }
    }

    func calendarCurrentPageDidChange(_ calendar: FSCalendar) {

        let selectedcomponents = self.currentCalendar.dateComponents([.month], from: calendar.currentPage)
        let currentComponents = self.currentCalendar.dateComponents([.month], from:  Date())

        guard let selectedMonth = selectedcomponents.month, let currentMonth = currentComponents.month else { return }

        if calendar.currentPage > Date() {
            previousBtn.isEnabled = true
        }else{
            if calendar.scope == .week {
                if currentMonth == selectedMonth {
                    previousBtn.isEnabled = true
                }else{
                    previousBtn.isEnabled = false
                }
            }else{
                previousBtn.isEnabled = false
            }
        }
    }

}

/*
 * Responsible for auto selecting/deselecting dates which is between start and end dates
 */
extension BookingPeriodVC {

    @objc func changeCalendarScope(scope: FSCalendarScope ) {
        self.calendar.setScope(scope, animated: true)
    }

    @objc func deselectDateStartingFrom(date: Date,selectedDates: [Date]) {

        let dates = selectedDates.sorted { $0.compare($1) == .orderedAscending }
        dates.forEach {
            if $0 > date {
                calendar.deselect($0)
            }
        }
    }

    fileprivate func selectAllDateBetween(start: Date, end: Date) {

        getMiddleDays(start: start, end: end).forEach {
            calendar.select($0)
            configureVisibleCells() // make it out of this scope
        }
    }

    fileprivate func getMiddleDays(start: Date, end: Date) -> [Date] {
        var fromDate = start
        let toDate = end

        var middleDates: [Date] = []

        if fromDate < toDate {
            while fromDate < toDate {
                if let nextDay = currentCalendar.date(byAdding: .day, value: 1, to: fromDate){
                    middleDates.append(nextDay)
                    fromDate = nextDay
                }
            }
        }else {
            while fromDate > toDate {
                if let previousDay = currentCalendar.date(byAdding: .day, value: -1, to: fromDate){
                    middleDates.append(previousDay)
                    fromDate = previousDay
                }

            }
        }
        return middleDates
    }

    fileprivate func disablePanOnCalendar(){
        DispatchQueue.main.asyncAfter(deadline: .now()+0.5) {
            self.calendar.scrollEnabled = false
        }
    }

    fileprivate func setPreviousAndNextForCurrentCalendarScope(value: Int) {
        if calendar.scope == .month {
            let date = currentCalendar.date(byAdding: .month, value: value, to:calendar.currentPage)!
            calendar.setCurrentPage(date, animated: true)
        }else {
            let date = currentCalendar.date(byAdding: .weekOfMonth, value: value, to:calendar.currentPage)!
            calendar.setCurrentPage(date, animated: true)
        }
    }
}

extension FSCalendar {

    @objc var getUserSelectedDate: [String] {

        var userSelectedDates:[Date] = []

        if selectedDates.count >= 2 {
            userSelectedDates = selectedDates.sorted { $0.compare($1) == .orderedAscending }
            guard let firstDate = userSelectedDates.first, let lastDate = userSelectedDates.last else {
                return []
            }
            return [firstDate,lastDate].map{ DateFormatter.getDateFor(type: .ddMMMyyyyE, date: $0) }
        }
        return selectedDates.map{ DateFormatter.getDateFor(type: .ddMMMyyyyE, date: $0) }
    }
}

enum DateFormatType: String {

    case dmy = "d MMM yyyy" // 5 Dec 2017
    case hmi = "h:mm a" // 12:20 AM
    case ddm = "E, dd MMM" // Tue 02 Dec
    case hm = "HH,mm" // 03:02
    case m = "M,y" // 1
    case ddMMMyyyyE = "dd MMM yyyy (E)"
    case yyyyMMdd = "yyyy-MM-dd"
}

extension DateFormatter {

    static func getDateFor(type: DateFormatType, date: Date) -> String {

        let formatter = DateFormatter()
        switch type {
        case .dmy:
            formatter.dateFormat = type.rawValue
        case .hmi:
            formatter.dateFormat = type.rawValue
        case .ddm:
            formatter.dateFormat = type.rawValue
        case .hm:
            formatter.dateFormat = type.rawValue
        case .m:
            formatter.dateFormat = type.rawValue
        case .ddMMMyyyyE:
            formatter.dateFormat = type.rawValue
        case .yyyyMMdd:
            formatter.dateFormat = type.rawValue
        }
        return formatter.string(from: date)
    }
}

The strange thing is that I did not get the red error, and the log message are confused for me. I'm quite new in iOS, please help me and be precisely what and where should I solve the issue. Thank you very much.

Edit: Final solution:

change

calender.subviews[1].backgroundColor = .white

to

calender.subviews.count >= 2 {
    calender.subviews[1].backgroundColor = .white
}

Solution

  • Your problem is in startSelectingDate First of all instantiate your viewController like this

    let vc: BookingPeriodVC = UIStoryboard(name: "EAN Scene", bundle: nil).instantiateViewControllerWithIdentifier("identifier for your controller") as BookingPeriodVC
     if let vc = UIStoryboard.bookingPeriodVC() as? BookingPeriodVC{
            vc.delegate = self
            vc.modalTransitionStyle = .crossDissolve
            vc.modalPresentationStyle = .overCurrentContext
            tabBarController?.present(vc, animated: true, completion: nil)
        }
    

    Secondly for calender.subviews[1].backgroundColor = .white place a check if

    calender.subviews.count >= 2 {
        calender.subviews[1].backgroundColor = .white
    }
    

    Thirdly you are trying to access first element of selectedDates in below line will give you crash as your calendar.getUserSelectedDate array is empty when you initiate viewController

    if let firstDate = calendar.getUserSelectedDate.first you have to put a check like

    if let calenderDates = calendar.getUserSelectedDate{
        if calenderDates.count>0{
            firstDate = calendar.getUserSelectedDate.first
        }
    }