2

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
}
darkfruitmilk
  • 79
  • 1
  • 8

2 Answers2

3

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
    }
}
Abu Ul Hassan
  • 1,340
  • 11
  • 28
  • Hi thank you very much for your kind help. I think what you say about the issue is correct. I tried your code. But got many errors when I change the code of firstDate = calendar.getUserSelectedDate.firsrt. It might because I used the wrong way and don't know how to apply your code. I tried @PrashantTukadiya's solution and it works. His way of solving this issue should be same as yours, although not the perfect solution. Any way, thank you very much for your time and effort. Your answer also helped a lot! – darkfruitmilk Apr 24 '19 at 08:06
  • And after trying only apply: calender.subviews.count >= 2 { calender.subviews[1].backgroundColor = .white } It also solved the issue. And it should be a better solution. Thanks – darkfruitmilk Apr 24 '19 at 08:29
  • Pleasure :) happy coding – Abu Ul Hassan Apr 24 '19 at 09:07
-1
let vc: BookingPeriodVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ViewController") as BookingPeriodVC

  vc.delegate = self
        vc.modalTransitionStyle = .crossDissolve
        vc.modalPresentationStyle = .overCurrentContext

self.presentViewController(vc, animated: false, completion: nil)

do like this...