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.
Here's the button I tapped:
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
}
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
}
}