Weather Swift Preferences - swift

I'm currently working on an app with xCode, i'm new to iOS. My weather format is currently submitting "Celcius" & "Fahrenheit" to my database instead of sending C or F. I have try several thing but it seem to look like it also change my display text to C or F. I want to keep the display text to Fehrenheit & Celcius. Here's my code:
import UIKit
import FirebaseAuth
import FirebaseDatabase
class PreferencesVC: UIViewController, UITextFieldDelegate,
UIPickerViewDataSource, UIPickerViewDelegate {
#IBOutlet weak var setTimeFormat: UITextField!
#IBOutlet weak var setUnitFormat: UITextField!
#IBOutlet weak var setLanguage: UITextField!
var timePicker = UIPickerView()
var unitPicker = UIPickerView()
var languagePicker = UIPickerView()
var timeData: [String] = [String]()
var unitData: [String] = [String]()
var langugageData: [String] = [String]()
var preferenceHandle: DatabaseHandle!
override func viewDidLoad() {
super.viewDidLoad()
self.hideKeyboardWhenTappedAround()
self.view.backgroundColor = UIColor(patternImage: UIImage(named: "background4.png")!)
guard Auth.auth().currentUser != nil else {
print("TEST: No current user")
performSegue(withIdentifier: "LoginVC", sender: nil)
return
}
self.setTimeFormat.delegate = self
self.setUnitFormat.delegate = self
self.setLanguage.delegate = self
timeData = ["12 Hour","24 Hour"]
unitData = ["Celcius", "Fahrenheit"]
langugageData = ["English","Francais"]
timePicker.dataSource = self
timePicker.delegate = self
unitPicker.dataSource = self
unitPicker.delegate = self
languagePicker.dataSource = self
languagePicker.delegate = self
setTimeFormat.inputView = timePicker
setUnitFormat.inputView = unitPicker
setLanguage.inputView = languagePicker
setTimeFormat.text = "12 Hour"
setUnitFormat.text = "Fahrenheit"
setLanguage.text = "English"
let userID = User.uid
self.preferenceHandle = DataService.instance.userRef.child(userID).child("UserPreferences").observe(DataEventType.value, with: { (snapshot) in
if let userPreference = snapshot.value as? Dictionary<String, AnyObject> {
if let timeFormat = userPreference["UserTimeFormat"] as? String {
User.timeFormat = timeFormat
switch(timeFormat){
case "12H":
self.setTimeFormat.text = "12 Hour"
break;
case "24H":
self.setTimeFormat.text = "24 hour"
break;
default:
self.setTimeFormat.text = "12 Hour"
break;
}
}
if let unitFormat = userPreference["UserTempFormat"] as? String {
User.unitFormat = unitFormat
switch(unitFormat){
case "F":
self.setUnitFormat.text = "Fahrenheit"
break;
case "C":
self.setUnitFormat.text = "Celcius"
break;
default:
self.setUnitFormat.text = "Fahrenheit"
break;
}
}
if let language = userPreference["UserLanguage"] as? String {
print("language = " , language)
User.language = language
switch(language){
case "English":
self.setLanguage.text = "English"
break;
case "Français":
self.setLanguage.text = "Français"
break;
default:
self.setLanguage.text = "English"
break;
}
}
}
})
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func cancelButtonPressed(_ sender: Any) {
print("THEY PRESSED THE CANCEL BUTTON MASTER")
DataService.instance.userRef.child(User.uid).child("UserPreferences").removeObserver(withHandle: preferenceHandle)
dismiss(animated: true, completion: nil)
}
#IBAction func submitButtonPressed(_ sender: Any) {
print("THEY PRESSED THE SUBMIT BUTTON MASTER")
var userPreferences: Dictionary<String,String> = [:]
if (self.setTimeFormat != nil) && setTimeFormat.text != "" {
if let timeFormat = setTimeFormat.text {
switch(timeFormat){
case "24 Hour":
userPreferences["UserTimeFormat"] = "24H"
break;
case "12 Hour":
userPreferences["UserTimeFormat"] = "12H"
break;
default:
userPreferences["UserTimeFormat"] = "12H"
break;
}
}
}
if (self.setUnitFormat != nil) && setUnitFormat.text != "" {
if let unitFormat = setUnitFormat.text {
switch(unitFormat){
case "Fahrenheit":
userPreferences["UserTempFormat"] = "F"
break;
case "Celcius":
userPreferences["UserTempFormat"] = "C"
break;
default:
userPreferences["UserTempFormat"] = "F"
break;
}
userPreferences["UserTempFormat"] = unitFormat
}
}
if (self.setLanguage != nil) && setLanguage.text != "" {
if let language = setLanguage.text {
userPreferences["UserLanguage"] = language
}
}
DataService.instance.userRef.child(User.uid).child("UserPreferences").updateChildValues(userPreferences)
DataService.instance.userRef.child(User.uid).child("UserPreferences").removeObserver(withHandle: preferenceHandle)
User.timeFormat = setTimeFormat.text!
User.unitFormat = setUnitFormat.text!
User.language = setLanguage.text!
dismiss(animated: true, completion: nil)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.view.endEditing(true)
return false
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
var count = 0
switch(pickerView){
case timePicker:
count = timeData.count
break
case unitPicker:
count = unitData.count
break
case languagePicker:
count = langugageData.count
break
default:
count = 0
break
}
return count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
switch(pickerView){
case timePicker:
return timeData[row]
case unitPicker:
return unitData[row]
case languagePicker:
return langugageData[row]
default:
return timeData[row]
}
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
switch(pickerView){
case timePicker:
setTimeFormat.text = timeData[row]
break
case unitPicker:
setUnitFormat.text = unitData[row]
break
case languagePicker:
setLanguage.text = langugageData[row]
break
default:
setTimeFormat.text = timeData[row]
break
}
}
}

Remove this row in submitButtonPressed since it overrides what has been set just before
userPreferences["UserTempFormat"] = unitFormat

Related

Why is my alert controller not dismissing in my Swift app?

I have a custom view controller class which presents an alert controller when it receives a not connected notification from my network services class. I want to dismiss the alert controller when I receive a further connected notification from the network services class but I am unable to dismiss the presenting alert.
Here is my custom view controller class;
class CompanyPriceListVC: UITableViewController {
// MARK: - Properties
let companyStockSymbols = ["AAPL","AMZN", "MSFT", "GOOGL", "TSLA", "META","COINBASE:BTC", "BINANCE:BTCUSDT", "BINANCE:BNBBTC", "IC MARKETS:1", "IC MARKETS:2"]
let defaultStockInfo = StockInfo(tradeConditions: nil, price: 0.00, symbol: "-", timestamp: 0.0, volume: 0.0)
lazy var companyStockInfo = [StockInfo](repeating: defaultStockInfo, count: companyStockSymbols.count)
var companyDetailsVC:CompanyDetailsVC?
var tableRowSelected: Int?
let defaultPriceHistory = PriceHistory(previous: nil, relative: 0.0, absolute: 0.0)
lazy var priceHistory = [PriceHistory](repeating: defaultPriceHistory , count: companyStockSymbols.count)
var deviceOrientation: Orientation = .portrait
let activityIndicator: UIActivityIndicatorView = {
let activityIndicator = UIActivityIndicatorView()
activityIndicator.style = .large
activityIndicator.hidesWhenStopped = true
activityIndicator.color = .white
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.startAnimating()
return activityIndicator
}()
let isConnected = Notification.Name(rawValue: isConnectedNotificationKey)
let isNotConnected = Notification.Name(rawValue: isDisconnectedNotificationKey)
let alertController = UIAlertController(title: "Connectivity Error", message: "Network connection lost. Please check your WiFi settings, mobile data settings or reception coverage.", preferredStyle: .alert)
// MARK: - Lifecycle Methods
override func viewDidLoad() {
super.viewDidLoad()
if UIDevice.current.orientation.isPortrait {
self.deviceOrientation = .portrait
self.tableView.register(CompanyStockCellPortrait.self, forCellReuseIdentifier: "CompanyStockCellPortrait")
} else {
self.deviceOrientation = .landscape
self.tableView.register(CompanyStockCellLandscape.self, forCellReuseIdentifier: "CompanyStockCellLandscape")
}
configureNavigationBar()
view.backgroundColor = .black
configureTableView()
configureConstraints()
createObservers()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
private func createObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(CompanyPriceListVC.dismissAndFetchStockInfo), name: isConnected, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(CompanyPriceListVC.notifyUserOnScreen(notification:)), name: isNotConnected, object: nil)
}
#objc private func fetchStockInfo() {
NetworkServices.sharedInstance.fetchStockInfo(symbols: companyStockSymbols, delegate: self)
}
#objc private func notifyUserOnScreen(notification: NSNotification) {
self.present(alertController, animated: true)
}
#objc public func dismissAndFetchStockInfo() {
print("DEBUG: isBeingPresented: \(alertController.isBeingPresented)")
if alertController.isBeingPresented {
self.alertController.dismiss(animated: true, completion: nil)
}
fetchStockInfo()
}
override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
super.willTransition(to: newCollection, with: coordinator)
coordinator.animate(alongsideTransition: { (context) in
guard let windowInterfaceOrientation = self.windowInterfaceOrientation else { return }
if windowInterfaceOrientation.isPortrait {
self.deviceOrientation = .portrait
self.tableView.register(CompanyStockCellPortrait.self, forCellReuseIdentifier: "CompanyStockCellPortrait")
self.tableView.reloadData()
} else {
self.deviceOrientation = .landscape
self.tableView.register(CompanyStockCellLandscape.self, forCellReuseIdentifier: "CompanyStockCellLandscape")
self.tableView.reloadData()
}
})
}
private var windowInterfaceOrientation: UIInterfaceOrientation? {
return self.view.window?.windowScene?.interfaceOrientation
}
// MARK: - TableView Data Source Methods
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return companyStockInfo.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch deviceOrientation {
case .portrait:
let tableViewCellPortrait = tableView.dequeueReusableCell(withIdentifier: "CompanyStockCellPortrait", for: indexPath) as! CompanyStockCellPortrait
tableViewCellPortrait.symbolValue.text = companyStockInfo[indexPath.row].symbol
var latestPrice = companyStockInfo[indexPath.row].price
latestPrice = round(latestPrice * 100.0) / 100.00
tableViewCellPortrait.priceValue.text = String(format: "%.2f", arguments:[latestPrice])
if let _ = priceHistory[indexPath.row].previous {
let absoluteDifference = priceHistory[indexPath.row].absolute
let relativeDifference = priceHistory[indexPath.row].relative
if absoluteDifference > 0.0 {
tableViewCellPortrait.priceDirectionImageView.image = UIImage(systemName: "arrowtriangle.up.fill")
tableViewCellPortrait.priceDirectionImageView.tintColor = .systemGreen
tableViewCellPortrait.relativePriceDiffValue.text = String(format: "%.2f%%", arguments: [relativeDifference])
tableViewCellPortrait.relativePriceDiffValue.textColor = .systemGreen
tableViewCellPortrait.absolutePriceDiffValue.text = String(format: "(%.2f)", arguments: [absoluteDifference])
tableViewCellPortrait.absolutePriceDiffValue.textColor = .systemGreen
} else if absoluteDifference < 0.0 {
tableViewCellPortrait.priceDirectionImageView.image = UIImage(systemName: "arrowtriangle.down.fill")
tableViewCellPortrait.priceDirectionImageView.tintColor = .systemRed
tableViewCellPortrait.relativePriceDiffValue.text = String(format: "%.2f%%", arguments: [relativeDifference])
tableViewCellPortrait.relativePriceDiffValue.textColor = .systemRed
tableViewCellPortrait.absolutePriceDiffValue.text = String(format: "(%.2f)", arguments: [absoluteDifference])
tableViewCellPortrait.absolutePriceDiffValue.textColor = .systemRed
} else {
tableViewCellPortrait.priceDirectionImageView.image = UIImage(systemName: "diamond.fill")
tableViewCellPortrait.priceDirectionImageView.tintColor = .white
tableViewCellPortrait.relativePriceDiffValue.text = "0.00%"
tableViewCellPortrait.relativePriceDiffValue.textColor = .white
tableViewCellPortrait.absolutePriceDiffValue.text = "(0.00)"
tableViewCellPortrait.absolutePriceDiffValue.textColor = .white
}
} else {
tableViewCellPortrait.priceDirectionImageView.image = UIImage()
tableViewCellPortrait.priceDirectionImageView.tintColor = .white
tableViewCellPortrait.relativePriceDiffValue.text = ""
tableViewCellPortrait.absolutePriceDiffValue.text = ""
}
return tableViewCellPortrait
case .landscape:
let tableViewCellLandscape = tableView.dequeueReusableCell(withIdentifier: "CompanyStockCellLandscape", for: indexPath) as! CompanyStockCellLandscape
tableViewCellLandscape.symbolValue.text = companyStockInfo[indexPath.row].symbol
var latestPrice = companyStockInfo[indexPath.row].price
latestPrice = round(latestPrice * 100.0) / 100.00
tableViewCellLandscape.priceValue.text = String(format: "%.2f", arguments:[latestPrice])
if let _ = priceHistory[indexPath.row].previous {
let absoluteDifference = priceHistory[indexPath.row].absolute
let relativeDifference = priceHistory[indexPath.row].relative
if absoluteDifference > 0.0 {
tableViewCellLandscape.priceDirectionImageView.image = UIImage(systemName: "arrowtriangle.up.fill")
tableViewCellLandscape.priceDirectionImageView.tintColor = .systemGreen
tableViewCellLandscape.relativePriceDiffValue.text = String(format: "%.2f%%", arguments: [relativeDifference])
tableViewCellLandscape.relativePriceDiffValue.textColor = .systemGreen
tableViewCellLandscape.absolutePriceDiffValue.text = String(format: "(%.2f)", arguments: [absoluteDifference])
tableViewCellLandscape.absolutePriceDiffValue.textColor = .systemGreen
} else if absoluteDifference < 0.0 {
tableViewCellLandscape.priceDirectionImageView.image = UIImage(systemName: "arrowtriangle.down.fill")
tableViewCellLandscape.priceDirectionImageView.tintColor = .systemRed
tableViewCellLandscape.relativePriceDiffValue.text = String(format: "%.2f%%", arguments: [relativeDifference])
tableViewCellLandscape.relativePriceDiffValue.textColor = .systemRed
tableViewCellLandscape.absolutePriceDiffValue.text = String(format: "(%.2f)", arguments: [absoluteDifference])
tableViewCellLandscape.absolutePriceDiffValue.textColor = .systemRed
} else {
tableViewCellLandscape.priceDirectionImageView.image = UIImage(systemName: "diamond.fill")
tableViewCellLandscape.priceDirectionImageView.tintColor = .white
tableViewCellLandscape.relativePriceDiffValue.text = "0.00%"
tableViewCellLandscape.relativePriceDiffValue.textColor = .white
tableViewCellLandscape.absolutePriceDiffValue.text = "(0.00)"
tableViewCellLandscape.absolutePriceDiffValue.textColor = .white
}
} else {
tableViewCellLandscape.priceDirectionImageView.image = UIImage()
tableViewCellLandscape.priceDirectionImageView.tintColor = .white
tableViewCellLandscape.relativePriceDiffValue.text = ""
tableViewCellLandscape.absolutePriceDiffValue.text = ""
}
return tableViewCellLandscape
}
}
// MARK: - TableView Delegate Methods
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
companyDetailsVC = CompanyDetailsVC(companyStockInfo: companyStockInfo[indexPath.row])
tableRowSelected = indexPath.row
navigationController?.pushViewController(companyDetailsVC!, animated: true)
}
// MARK: - Helper Functions
private func configureNavigationBar() {
UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
navigationController?.navigationBar.topItem?.title = "Trade Tracker"
navigationController?.navigationBar.barStyle = .black
navigationController?.navigationBar.prefersLargeTitles = true
}
private func configureTableView() {
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .singleLine
tableView.separatorColor = .white
tableView.tableFooterView = UIView()
}
private func configureConstraints() {
tableView.addSubview(activityIndicator)
NSLayoutConstraint.activate([
activityIndicator.centerXAnchor.constraint(equalTo: tableView.centerXAnchor),
activityIndicator.topAnchor.constraint(equalTo: tableView.topAnchor, constant: 250)
])
tableView.layoutSubviews()
}
}
// MARK: - Network Service Delegate Method
extension CompanyPriceListVC: NetworkServicesDelegate {
func sendStockInfo(stocksInfo: [String : StockInfo]) {
DispatchQueue.main.async {
// Compute absolute and relative price differences
for (index, symbol) in self.companyStockSymbols.enumerated() {
if stocksInfo[symbol] != nil {
self.priceHistory[index].previous = self.companyStockInfo[index].price
self.companyStockInfo[index] = stocksInfo[symbol]!
var latestPrice = self.companyStockInfo[index].price
latestPrice = round(latestPrice * 100.0) / 100.00
if let previous = self.priceHistory[index].previous {
let previousPrice = round(previous * 100.0) / 100.0
var absoluteDifference = latestPrice - previousPrice
absoluteDifference = round(absoluteDifference * 100.0) / 100.0
self.priceHistory[index].absolute = absoluteDifference
var relativeDifference = 0.0
if previousPrice != 0.00 {
relativeDifference = (absoluteDifference / previousPrice) * 100.0
relativeDifference = round(relativeDifference * 100) / 100.0
}
self.priceHistory[index].relative = relativeDifference
}
}
}
self.tableView.reloadData()
self.activityIndicator.stopAnimating()
guard let rowSelected = self.tableRowSelected else { return }
self.companyDetailsVC!.companyStockInfo = self.companyStockInfo[rowSelected]
self.companyDetailsVC!.relativeDifference = self.priceHistory[rowSelected].relative
self.companyDetailsVC!.absoluteDifference = self.priceHistory[rowSelected].absolute
}
}
}
and here is my network services class;
import UIKit
import Starscream
import Network
protocol NetworkServicesDelegate: AnyObject {
func sendStockInfo(stocksInfo: [String: StockInfo])
}
final class NetworkServices {
static let sharedInstance = NetworkServices()
var request = URLRequest(url: FINNHUB_SOCKET_STOCK_INFO_URL!)
var socket: WebSocket!
public private(set) var isConnected = false
var stocksInfo: [String: StockInfo] = [:]
var socketResults: [String: [StockInfo]] = [:]
weak var delegate: NetworkServicesDelegate?
var stockSymbols: [String] = []
private init() {
request.timeoutInterval = 5
socket = WebSocket(request: request)
socket.delegate = self
}
private let queue = DispatchQueue.global()
private let monitor = NWPathMonitor()
public func startMonitoring() {
monitor.start(queue: queue)
self.monitor.pathUpdateHandler = { [weak self] path in
if path.status == .satisfied {
// connect the socket
self?.socket.connect()
} else {
// disconnect the socket
self?.socket.disconnect()
// post notification that socket is now disconnected
DispatchQueue.main.async {
let name = Notification.Name(rawValue: isDisconnectedNotificationKey)
NotificationCenter.default.post(name: name, object: nil)
}
}
}
}
public func stopMonitoring() {
monitor.cancel()
}
func fetchStockInfo(symbols: [String], delegate: CompanyPriceListVC) {
stockSymbols = symbols
self.delegate = delegate
for symbol in symbols {
let string = FINNHUB_SOCKET_MESSAGE_STRING + symbol + "\"}"
socket.write(string: string)
}
}
private func parseJSONSocketData(_ socketString: String) {
self.socketResults = [:]
self.stocksInfo = [:]
let decoder = JSONDecoder()
do {
let socketData = try decoder.decode(SocketData.self, from: socketString.data(using: .utf8)!)
guard let stockInfoData = socketData.data else { return }
for stockInfo in stockInfoData {
let symbol = stockInfo.symbol
if self.socketResults[symbol] == nil {
self.socketResults[symbol] = [StockInfo]()
}
self.socketResults[symbol]?.append(stockInfo)
}
for (symbol, stocks) in self.socketResults {
for item in stocks {
if self.stocksInfo[symbol] == nil {
self.stocksInfo[symbol] = item
} else if item.timestamp > self.stocksInfo[symbol]!.timestamp {
self.stocksInfo[symbol] = item
}
}
}
self.delegate?.sendStockInfo(stocksInfo: self.stocksInfo)
} catch {
print("DEBUG: error: \(error.localizedDescription)")
}
}
func fetchCompanyInfo(symbol: String, completion: #escaping (CompanyInfo?, UIImage?)->()) {
let urlString = FINNHUB_HTTP_COMPANY_INFO_URL_STRING + symbol + "&token=" + FINNHUB_API_TOKEN
guard let url = URL(string: urlString) else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print("Error fetching company info: \(error)")
}
guard let data = data else { return }
let decoder = JSONDecoder()
do {
let companyInfo = try decoder.decode(CompanyInfo.self, from: data)
guard let logoURL = URL(string: companyInfo.logo) else { return }
let task = URLSession.shared.dataTask(with: logoURL) { data, response, error in
if let error = error {
print("Error fetching logo image: \(error)")
}
guard let data = data else { return }
guard let logoImage = UIImage(data: data) else { return }
completion(companyInfo, logoImage)
}
task.resume()
} catch {
print("Error decoding JSON: \(error)")
completion(nil, nil)
}
}
task.resume()
}
}
extension NetworkServices: WebSocketDelegate {
func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event {
case .connected(_):
self.isConnected = true
// post notification that socket is now connected
let name = Notification.Name(rawValue: isConnectedNotificationKey)
NotificationCenter.default.post(name: name, object: nil)
case .disconnected(let reason, let code):
print("DEBUG: Got disconnected.")
self.isConnected = false
case .cancelled:
print("DEBUG: cancelled.")
// post notification that socket is not connected
case .reconnectSuggested(let suggestReconnect):
print("DEBUG: suggestReconnect = \(suggestReconnect)")
// post notification that socket is not connected
case .viabilityChanged(let viabilityChanged):
print("DEBUG: viabilityChange = \(viabilityChanged)")
case .error(let error):
print("DEBUG error: \(String(describing: error?.localizedDescription))")
case .text(let socketString):
parseJSONSocketData(socketString)
default:
break
}
}
}
I've tried querying the isBeingPresented property of the alert controller but it always tests as false even though I can see the alert controller is being presented.
You could do a check if the currently presented UIViewController is actually the alertController like this:
#objc public func dismissAndFetchStockInfo() {
if presentedViewController == alertController {
alertController.dismiss(animated: true, completion: nil)
}
fetchStockInfo()
}
This is because isBeingPresented is only valid inside the view[Will|Did][Disa|A]ppear methods.

RxDatasource in RxSwift reload animation don't update data source

RxDatasource in RxSwift [RxTableViewSectionedAnimatedDataSource] Reload Animation don't update data source. What mistake I am doing? I am even unable to bind my action with button properly.
TableDataSource and Table editing commands
struct TableDataSource {
var header: String
var items: [Item]
var SectionViewModel: SectionViewModel
}
extension TableDataSource: AnimatableSectionModelType {
var identity: String {
return header
}
type alias Item = Data
init(original: TableDataSource, items: [Item]) {
self = original
self.items = items
self.sectionViewModel = original.sectionViewModel
}
}
enum TableViewEditingCommand {
case deleteSingle(IndexPath)
case clearAll(IndexPath)
case readAll(IndexPath)
}
struct SectionedTableViewState {
var sections: [TableDataSource]
init(sections: [TableDataSource]) {
self.sections = sections
}
func execute(command: TableViewEditingCommand) -> SectionedTableViewState {
var sections = self.sections
switch command {
// Delete single item from datasource
case .deleteSingle(let indexPath):
var items = sections[indexPath.section].items
items.remove(at: indexPath.row)
if items.count <= 0 {
sections.remove(at: indexPath.section)
} else {
sections[indexPath.section] = TableDataSource(
original: sections[indexPath.section],
items: items) }
// Clear all item from datasource with isUnread = false
case .clearAll(let indexPath):
sections.remove(at: indexPath.section)
// Mark all item as read in datasource with isUnread = true
case .readAll(let indexPath):
var items = sections[indexPath.section].items
items = items.map { var unread = $0
if $0.isUnRead == true { unreadData.isUnRead = false }
return unreadData
}
sections.remove(at: indexPath.section)
if sections.count > 0 {
let allReadItems = sections[indexPath.section].items + items
sections[indexPath.section] = TableDataSource(
original: sections[indexPath.section],
items: allReadItems)
}
}
return SectionedTableViewState(sections: sections)
}
}
This is my controller and its extensions
class ViewController: UIViewController, Storyboardable {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var closeButton: UIButton!
#IBOutlet weak var titleText: UILabel!
var viewModel: ViewModel!
let disposeBag = DisposeBag()
let sectionHeight: CGFloat = 70
let dataSource = ViewController.dataSource()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
bindInitials()
bindDataSource()
bindDelete()
}
private func bindInitials() {
tableView.delegate = nil
tableView.rx.setDelegate(self)
.disposed(by: disposeBag)
registerNibs()
}
private func registerNibs() {
let headerNib = UINib.init(
nibName: TableViewSection.identifier,
bundle: nil)
tableView.register(
headerNib,
forHeaderFooterViewReuseIdentifier: TableViewSection.identifier)
}
}
extension ViewController: Bindable {
func bindViewModel() {
bindActions()
}
private func bindDataSource() {
bindDelete()
// tableView.dataSource = nil
// Observable.just(sections)
// .bind(to: tableView.rx.items(dataSource: dataSource))
// .disposed(by: disposeBag)
}
private func bindDelete() {
/// TODO: to check and update delete code to work properly to sink with clear all and mark all as read
guard let sections = self.viewModel?.getSections() else {
return
}
let deleteState = SectionedTableViewState(sections: sections)
let deleteCommand = tableView.rx.itemDeleted.asObservable()
.map(TableViewEditingCommand.deleteSingle)
tableView.dataSource = nil
Observable.of(deleteCommand)
.merge()
.scan(deleteState) {
(state: SectionedTableViewState,
command: TableViewEditingCommand) -> SectionedTableViewState in
return state.execute(command: command) }
.startWith(deleteState) .map { $0.sections }
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
private func bindActions() {
guard let openDetailsObserver = self.viewModel?.input.openDetailsObserver,
let closeObserver = self.viewModel?.input.closeObserver else {
return
}
viewModel.output.titleTextDriver
.drive(titleText.rx.text)
.disposed(by: disposeBag)
// viewModel.input.dataSourceObserver
// .mapObserver({ (result) -> [Data] in
// return result
// })
// .disposed(by: disposeBag)
/// Close button binding with closeObserver
closeButton.rx.tap
.bind(to: (closeObserver))
.disposed(by: disposeBag)
/// Tableview item selected binding with openDetailsObserver
tableView.rx.itemSelected
.map { indexPath in
return (self.dataSource[indexPath.section].items[indexPath.row])
}.subscribe(openDetailsObserver).disposed(by: disposeBag)
}
}
extension ViewController: UITableViewDelegate {
static func dataSource() -> RxTableViewSectionedAnimatedDataSource<TableDataSource> {
return RxTableViewSectionedAnimatedDataSource(
animationConfiguration: AnimationConfiguration(insertAnimation: .fade,
reloadAnimation: .fade,
deleteAnimation: .fade),
configureCell: { (dataSource, table, idxPath, item) in
var cell = table.dequeueReusableCell(withIdentifier: TableViewCell.identifier) as? TableViewCell
let cellViewModel = TableCellViewModel(withItem: item)
cell?.setViewModel(to: cellViewModel)
return cell ?? UITableViewCell()
}, canEditRowAtIndexPath: { _, _ in return true })
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard var headerView = tableView.dequeueReusableHeaderFooterView(
withIdentifier: TableViewSection.identifier)
as? TableViewSection
else { return UITableViewHeaderFooterView() }
let viewModel = self.dataSource[section].sectionViewModel
headerView.setViewModel(to: viewModel)
headerView.dividerLine.isHidden = section == 0 ? true : false
headerView.section = section
let data = TableViewEditingCommand.clearAll(IndexPath(row: 0, section: section ?? 0))
// /// Section button binding with closeObserver
// headerView.sectionButton.rx.tap
// .map(verseNum -> TableViewEditingCommand in (TableViewEditingCommand.deleteSingle))
// .disposed(by: disposeBag)
headerView.sectionButtonTappedClosure = { [weak self] (section, buttonType) in
if buttonType == ButtonType.clearAll {
self?.showClearAllConfirmationAlert(section: section, buttonType: buttonType)
} else {
self?.editAction(section: section, buttonType: buttonType)
}
}
return headerView
}
func editAction(section: Int, buttonType: ButtonType) {
var sections = self.dataSource.sectionModels
let updateSection = (sections.count == 1 ? 0 : section)
switch buttonType {
/// Clear all
case .clearAll:
sections.remove(at: updateSection)
let data = SectionedTableViewState(sections: sections)
self.tableView.dataSource = nil
Observable.of(data)
.startWith(data) .map { $0.sections }
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
/// Mark all as read
case .markAllAsRead:
if updateSection == 0 { sections = self.viewModel.getSections() }
var items = sections[updateSection].items
items = items.map { var unread = $0
if $0.isUnRead == true { unread.isUnRead = false }
return unread
}
sections.remove(at: updateSection)
let allReadItems = sections[updateSection].items + items
sections[updateSection] = TableDataSource(
original: sections[updateSection],
items: allReadItems)
let data = SectionedTableViewState(sections: sections)
self.tableView.dataSource = nil
Observable.of(data)
.startWith(data) .map { $0.sections }
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
}
func showClearAllConfirmationAlert(section: Int, buttonType: ButtonType) {
let alert = UIAlertController(title: "Clear All",
message: "Are you sure, you want to clear all Items?",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in
switch action.style{
case .default:
self.editAction(section: section, buttonType: buttonType)
case .cancel: break
case .destructive: break
default:break
}}))
let cancel = UIAlertAction(title: "Cancel", style: .default, handler: { action in
})
alert.addAction(cancel)
self.present(alert, animated: true, completion: nil)
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return sectionHeight
}
}
View model for controller
class ViewModel {
private enum Constants {
static let titleText = "Test".localized
static let testHistoryHeaderText = "test".localized
static let unreadHeaderText = "Unread".localized
}
struct Input {
let dataSourceObserver: AnyObserver<[Data]>
let openDetailsObserver: AnyObserver<Data>
let closeObserver: AnyObserver<Void>
let sectionButtonTappedObserver: AnyObserver<IndexPath>
}
struct Output {
let titleTextDriver: Driver<String>
let dataSourceDriver: Driver<[Data]>
let viewComplete: Observable<DataCoordinator.Event>
}
let input: Input
let output: Output
private let dataSourceSubject = PublishSubject<[Data]>()
private let closeSubject = PublishSubject<Void>()
private let openDetailsSubject = BehaviorSubject<Data>(value:Data())
private let sectionButtonTappedSubject = PublishSubject<IndexPath>()
private let disposeBag = DisposeBag()
init(withRepository repository: Repository) {
input = Input(
dataSourceObserver: dataSourceSubject.asObserver(),
openDetailsObserver: openDetailsSubject.asObserver(),
closeObserver: closeSubject.asObserver(), sectionButtonTappedObserver: sectionButtonTappedSubject.asObserver()
)
let closeEventObservable = closeSubject.asObservable().map { _ in
return Coordinator.Event.goBack
}
let openDetailsEventObservable = openDetailsSubject.asObservable().map { _ in
return Coordinator.Event.goToDetails
}
let viewCompleteObservable = Observable.merge(closeEventObservable, openDetailsEventObservable)
let list = ViewModel.getData(repository: repository)
output = Output(
titleTextDriver: Observable.just(Constants.titleText).asDriver(onErrorJustReturn: Constants.titleText),
dataSourceDriver: Observable.just(list).asDriver(onErrorJustReturn: list),
viewComplete: viewCompleteObservable)
}
///TODO: To be updated as per response after API integration
static func getData(repository: Repository) -> [Data] {
return repository.getData()
}
func getSections() -> [TableDataSource] {
let List = ViewModel.getData(repository: Repository())
let unread = list.filter { $0.isUnRead == true }
let read = list.filter { $0.isUnRead == false }
let headerText = String(format:Constants.unreadHeaderText, unread.count)
let sections = [TableDataSource(
header: headerText,
items: unread,
sectionViewModel: SectionViewModel(
withHeader: headerText,
buttonType: ButtonType.markAllAsRead.rawValue,
items: unread)),
TableDataSource(
header: Constants.historyHeaderText,
items: read,
SectionViewModel: SectionViewModel(
withHeader: Constants.historyHeaderText,
buttonType: ButtonType.clearAll.rawValue,
items: read))]
return sections
}
}
i think your problem is with :
var identity: String {
return header
}
make a uuid and pass it to identity:
let id = UUID().uuidString
var identity: String {
return id
}

Fatal Error: Index Out of Range(UIPickerView)

I have a UIPickerView that is used to play the game Go Fish so data in it is continuously changing. AFter a few selections with the array I eventually get the error "Fatal Error: Index Out of Range" in the console and the error "thread 1 exc_bad_instruction (code=exc_i386_invop subcode=0x0)" on another line. Any idea on what is causing this and how to fix it.
Code:
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
///Main Section///
//Game
#IBOutlet weak var GameCardsLeft: UILabel!
//CPU
#IBOutlet weak var CPUCardsLeft: UILabel!
#IBOutlet weak var CPUPairs: UILabel!
//Player
#IBOutlet weak var UserPairs: UILabel!
#IBOutlet weak var pickerView: UIPickerView!
//Variables
var deck = [Card]()
//Creates a deck of cards
func createDeck() {
var i = 0 //Suite
while(i < 2) {
var s = "" //suite
var x = 1 //Value
if(i == 0) {
s = "Clubs"
}/* else if(i == 1) {
s = "Spades"
} else if(i == 2) {
s = "Diamonds"
}*/ else {
s = "Hearts"
}
while(x < 14) {
let tempCard = Card()
tempCard.value = x
tempCard.suite = s
tempCard.assignRank(value: tempCard.value)
tempCard.setDescription(rank: tempCard.rank, suite: tempCard.suite)
x += 1
deck.append(tempCard)
}
i += 1
}
}
//Shuffles the deck
func shuffle() {
var tempDeck = [Card]()
while deck.count > 0 {
let random = Int(arc4random_uniform(UInt32(deck.count)))
let card = deck.remove(at: random)
tempDeck.append(card)
}
deck = tempDeck
}
///Go Fish Section///
var drawDeck = [Card]()
var playerDeck = [Card]()
var playerPairsArray = [Card]()
var cpuDeck = [Card]()
var cpuPairsArray = [Card]()
var selectedCard = Card()
var gameOver = false
var randomIndex = 0
func deal() {
for card in deck {
if(playerDeck.count < 5) {
playerDeck.append(card)
} else if(cpuDeck.count < 5) {
cpuDeck.append(card)
} else {
drawDeck.append(card)
}
}
}
//Player draw card
func playerDraw() {
if(playerDeck.count == 0) {
while(playerDeck.count != 7) {
if(drawDeck.count > 0) {
playerDeck.append(drawDeck[0])
drawDeck.remove(at: 0)
} else {
break
}
}
} else {
if(drawDeck.count > 0) {
playerDeck.append(drawDeck[0])
drawDeck.remove(at: 0)
}
}
}
//CPU draw card
func cpuDraw() {
if(cpuDeck.count == 0) {
while(cpuDeck.count != 7) {
if(drawDeck.count > 0) {
cpuDeck.append(drawDeck[0])
drawDeck.remove(at: 0)
} else {
break
}
}
} else {
if(drawDeck.count > 0) {
cpuDeck.append(drawDeck[0])
drawDeck.remove(at: 0)
}
}
}
//Player's turn check cpu hand
func checkCPUHand(selectedCard: Card) {
var x = 0
while(x < cpuDeck.count) {
if(selectedCard.value == cpuDeck[x].value) {
playerDeck.append(cpuDeck[x])
cpuDeck.remove(at: x)
checkForPlayerPairs()
return
} else {
x += 1
}
}
playerDraw()
}
//CPU's turn check player hand
func checkPlayerHand(selectedCard: Card) {
var x = 0
while(x < playerDeck.count) {
if(selectedCard.value == playerDeck[x].value) {
cpuDeck.append(playerDeck[x])
playerDeck.remove(at: x)
checkForCPUPairs()
return
} else {
x += 1
}
}
cpuDraw()
}
//Check for player Pairs
func checkForPlayerPairs() {
var i = 0
var k = 0
var pairFound = false
while(i < playerDeck.count) {
pairFound = false
k = i + 1
while(k < playerDeck.count) {
if(playerDeck[i].value == playerDeck[k].value) {
pairFound = true
break
} else {
k += 1
}
}
if(pairFound) {
playerPairsArray.append(playerDeck[i])
playerDeck.remove(at: k)
playerDeck.remove(at: i)
} else {
i += 1
}
}
}
//Check for cpu pairs
func checkForCPUPairs() {
var i = 0
var k = 0
var pairFound = false
while(i < cpuDeck.count) {
pairFound = false
k = i + 1
while(k < cpuDeck.count) {
if(cpuDeck[i].value == cpuDeck[k].value) {
pairFound = true
break
} else {
k += 1
}
}
if(pairFound) {
cpuPairsArray.append(cpuDeck[i])
cpuDeck.remove(at: k)
cpuDeck.remove(at: i)
} else {
i += 1
}
}
}
/* Start of PickerView Stuff */
//Returns the number of columns
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
//Returns the number of rows
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return playerDeck.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return playerDeck[row].description //Error is in this line(thread 1 exc_bad_instruction (code=exc_i386_invop subcode=0x0))
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
selectedCard = playerDeck[row]
//have var = pickerData[row]
}
/* End of PickerView Stuff */
func organizeCardDeck() {
playerDeck = playerDeck.sorted { $0.value < $1.value }
cpuDeck = cpuDeck.sorted { $0.value < $1.value }
}
func start() {
createDeck()
shuffle()
shuffle()
deal()
organizeCardDeck()
checkForPlayerPairs()
checkForCPUPairs()
GameCardsLeft.text = "\(drawDeck.count)"
CPUCardsLeft.text = "\(cpuDeck.count)"
CPUPairs.text = "\(cpuPairsArray.count)"
UserPairs.text = "\(playerPairsArray.count)"
}
func checkGameOver() {
if(cpuDeck.count == 0 && playerDeck.count == 0 && drawDeck.count == 0) {
let cpuPairCount = cpuPairsArray.count
let playerPairCount = playerPairsArray.count
playerDeck.removeAll()
cpuDeck.removeAll()
deck.removeAll()
cpuPairsArray.removeAll()
playerPairsArray.removeAll()
/*if(playerPairCount > cpuPairCount) {
let alertController = UIAlertController(title: "Game Over", message: "You win", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "Restart", style: .default, handler: nil)
alertController.addAction(defaultAction)
} else if(playerPairCount < cpuPairCount) {
let alertController = UIAlertController(title: "Game Over", message: "You lose", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "Restart", style: .default, handler: nil)
alertController.addAction(defaultAction)
} else if(playerPairCount == cpuPairCount) {
let alertController = UIAlertController(title: "Game Over", message: "Tie game", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "Restart", style: .default, handler: nil)
alertController.addAction(defaultAction)
}*/
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.pickerView.delegate = self
self.pickerView.dataSource = self
/*var alert = UIAlertController(title: "Go FIsh Go", message: "", preferredStyle: .alert)
var startAlert = UIAlertAction(title: "Start", style: .default, handler: nil)
alert.addAction(startAlert)
present(alert, animated: true, completion: nil)*/
start()
}
#IBAction func SelectCard(_ sender: UIButton) {
checkCPUHand(selectedCard: selectedCard)
checkForPlayerPairs()
pickerView.reloadAllComponents()
pickerView.selectRow(0, inComponent: 0, animated: true)
if(playerDeck.count == 0 && drawDeck.count > 0) {
playerDraw()
}
GameCardsLeft.text = "\(drawDeck.count)"
CPUCardsLeft.text = "\(cpuDeck.count)"
CPUPairs.text = "\(cpuPairsArray.count)"
UserPairs.text = "\(playerPairsArray.count)"
randomIndex = Int(arc4random_uniform(UInt32(cpuDeck.count)))
checkPlayerHand(selectedCard: cpuDeck[randomIndex])
checkForCPUPairs()
if(cpuDeck.count == 0 && drawDeck.count > 0) {
cpuDraw()
}
GameCardsLeft.text = "\(drawDeck.count)"
CPUCardsLeft.text = "\(cpuDeck.count)"
CPUPairs.text = "\(cpuPairsArray.count)"
UserPairs.text = "\(playerPairsArray.count)"
checkGameOver()
}
}
Change return playerDeck[row].description to: return (row < playerDeck.count ? playerDeck[row].description : nil), and change selectedCard = playerDeck[row] to:
if row < playerDeck.count {
self.selectedCard = playerDeck[row]
}
Also, call self.pickerView.reloadAllComponents() right before returning from your deal, playerDraw, checkCPUHand, checkPlayerHand, checkForPlayerPairs, organizeCardDeck, and checkGameOver functions, and any other functions that modify playerDeck.

Search using NSpredicate

I am making an app for smartsearch. I have implemented through making parameters into string then append the strings.Then for search i am implementing filter to search from the search. But its getting lengthy. I want it in compact using NSPredicate.
My code is here. Can anyone make it using NSPredicate and NSCompoundPredicate
import UIKit
struct Request{
var type:String!
var firstName:String!
var lastName:String!
var combineString:String {
get {
let text = "\(self.type!) \(self.firstName) \(self.lastName)"
return text
}
}
init() {
self.type = ""
self.firstName = ""
self.lastName = ""
}
init(_ type:String,_ firstName:String,_ lastName:String) {
self.type = type
self.firstName = firstName
self.lastName = lastName
}}
class ViewController: UIViewController,UITableViewDelegate, UITableViewDataSource {
#IBOutlet var searchbar:UISearchBar!
#IBOutlet var headline:UILabel!
#IBOutlet var table:UITableView!
var type:Array = [String]()
var firstName:Array = [String]()
var lastName:Array = [String]()
var tempResults = Array<[Request]>()
var requestArray = Array<Request>()
var filteredArray = Array<Request>()
var isSearching = false
type = [“4343”, “45450”, “68468”, “4686”, “56461”, “48661”]
firstName = [“Tom”, “Michel”, “Steve”, “Rahul”, “Iswar”, “Kuldeep”]
lastName = [“Moody”, “Clark”, “Smith”, “Kumar”, “Pandey”, “Yadav”]
for i in 0..<requestTypeArr.count {
let type = self.type[i]
let firstName = self.firstName[i]
let lastName = self.lastName[i]
var request = Request()
request.type = type
request.firstName = firstName
request.lastName = lastName
self.requestArray.append(request)
}
let string = String(format: "Total list No %ld", self.requestArray.count)
self.headline.text = string
self.table.delegate = self
self.table.dataSource = self
self.table.contentInset = UIEdgeInsets(top: 44, left: 0, bottom: 0, right: 0)
self.table.estimatedRowHeight = 44
self.table.rowHeight = UITableViewAutomaticDimension
self.searchbar.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.isSearching == true {
return self.filteredArray.count
}
return self.requestArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RquestDetailTableViewCell") as! RquestDetailTableViewCell
var request = self.requestArray[indexPath.row]
if self.isSearching == true {
request = self.filteredArray[indexPath.row]
}
cell.type.text = request.type
cell.firstName.text = request.firstName
cell.lastName.text = request.lastName
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}}
extension ViewController : UISearchBarDelegate {
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
self.isSearching = true
// self.table.reloadSections([0], with: .automatic)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
//Filter one
//print("Search Query:- \(searchText)")
if searchText.components(separatedBy: .whitespaces).joined(separator: "").characters.count == 0 {
if self.isSearching == true {
self.isSearching = false
self.filteredArray = Array<Request>()
self.tempResults = Array<[Request]>()
}
}else {
if self.isSearching == false {
self.isSearching = true
}
var temp = Array<Request>()
if self.tempResults.count == 0 {
temp = self.requestArray.filter({ (object) -> Bool in
//print("\nSearch Text = \(searchText.lowercased()) \nand Source Combine String = \(object.combineString.lowercased())")
let lowercaseInput = searchText.lowercased()
let lowercaseSource = object.combineString.lowercased()
let sourceComponents = lowercaseSource.components(separatedBy: .whitespaces)
let inputComponents = lowercaseInput.components(separatedBy: .whitespaces)
let sourceSet = Set(sourceComponents)
var inputSet = Set(inputComponents)
if let index = inputSet.index(of: ""){
_ = inputSet.remove(at: index)
}
var filtered = [String]()
inputSet.forEach({ (word) in
let temp = sourceSet.filter({$0.contains(word)})
//let temp2 = sourceSet.filter({$0.hasPrefix(word)})
filtered.append(contentsOf: temp)
// let temp2 = sourceSet.filter({$0.hasPrefix(word)})
//filtered.append(contentsOf: temp2)
})
let set = Set(filtered)
var isOK = set.isSubset(of: sourceSet)
if set.count == 0 {
isOK = false
}
return isOK
})
self.filteredArray = temp
}else {
if let last = self.tempResults.last {
temp = last.filter({ (object) -> Bool in
let lowercaseInput = searchText.lowercased()
let lowercaseSource = object.combineString.lowercased()
let lastInputWord = lowercaseInput.components(separatedBy: .whitespaces).last!
let sourceComponents = lowercaseSource.components(separatedBy: .whitespaces)
let inputComponents = [lastInputWord]
let sourceSet = Set(sourceComponents)
let inputSet = Set(inputComponents)
var filtered = [String]()
inputSet.forEach({ (word) in
let temp = sourceSet.filter({$0.contains(word)})
//let temp2 = sourceSet.filter({$0.hasPrefix(word)})
filtered.append(contentsOf: temp)
})
let set = Set(filtered)
var isOK = set.isSubset(of: sourceSet)
if set.count == 0 {
isOK = false
}
return isOK
})
self.filteredArray = temp
}
}
}
self.table.reloadSections([0], with: .automatic)
}
func searchBar(_ searchBar: UISearchBar, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == " " {
self.tempResults.append(self.filteredArray)
}
if range.length == 1 {
//Handle Backspace/delete
let string = searchBar.text! as NSString
let temp = string.substring(with: range) as String
let newRange = string.range(of: temp, options: .backwards)
let newText = string.replacingCharacters(in: newRange, with: "")
let words = newText.components(separatedBy: .whitespaces).count
if words > 1 {
print("word count = \(words)")
let count = self.tempResults.count
if count > words {
self.tempResults.removeLast()
}
}else {
self.tempResults = Array<[Request]>()
}
}
return true
}
}

fatal error: Array index out of range. Swift when refresh

Tried so many times to find out what causes the fatal error. But, still can't figure it out. The first table (result table) causes this error when I try to refresh the table with pull. The second table (favoriteProductTableView) works perfect, so I didn't put any code about the second one. Wondering why. Thank you for your help.
var followArray = [String]()
var resultsNameArray = [String]()
var resultsImageFiles = [PFFile?]()
var resultsDetailsArray = [String]()
var resultsDetailsImageFiles = [PFFile?]()
var resultsObjectID = [String]()
var resultsTitle = [String]()
var personPriceArray = [String]()
var personQuantityArray = [String]()
var personOrderTypeArray = [String]()
var refresher:UIRefreshControl!
override func viewDidLoad() {
super.viewDidLoad()
favoriteProductTableView.hidden = true
refresher = UIRefreshControl()
refresher.tintColor = UIColor.blackColor()
refresher.addTarget(self, action: "refresh", forControlEvents: UIControlEvents.ValueChanged)
self.resultsTable.addSubview(refresher)
}
override func viewDidAppear(animated: Bool) {
refreshResults()
}
func refresh(){
refreshResults()
}
func refreshResults(){
switch(segmentedControl.selectedSegmentIndex){
case 0:
followArray.removeAll(keepCapacity: false)
resultsNameArray.removeAll(keepCapacity: false)
resultsImageFiles.removeAll(keepCapacity: false)
resultsDetailsArray.removeAll(keepCapacity: false)
resultsDetailsImageFiles.removeAll(keepCapacity: false)
resultsObjectID.removeAll(keepCapacity: false)
resultsTitle.removeAll(keepCapacity: false)
personPriceArray.removeAll(keepCapacity: false)
personQuantityArray.removeAll(keepCapacity: false)
personOrderTypeArray.removeAll(keepCapacity: false)
let followQuery = PFQuery(className: "follow")
followQuery.whereKey("user", equalTo: (PFUser.currentUser()!.username)!)
followQuery.whereKey("userToFollow", notEqualTo: (PFUser.currentUser()!.username)!)
followQuery.findObjectsInBackgroundWithBlock { (objects:[PFObject]?, error: NSError?) -> Void in
if error != nil {
}
for object in objects! {
self.followArray.append(object.objectForKey("userToFollow") as! String)
}
let query = PFQuery(className: "products")
query.whereKey("userName", containedIn: self.followArray)
query.findObjectsInBackgroundWithBlock { (catchobjects:[PFObject]?, error:NSError?) -> Void in
if error != nil {
}
for catchobject in catchobjects! {
if catchobject.objectForKey("selling_price") != nil {
self.personPriceArray.append(catchobject.objectForKey("selling_price") as! String)
self.personOrderTypeArray.append("Selling")
} else {
self.personPriceArray.append(catchobject.objectForKey("buying_price") as! String)
self.personOrderTypeArray.append("Buying")
}
self.personQuantityArray.append(catchobject.objectForKey("quantity") as! String)
self.resultsNameArray.append(catchobject.objectForKey("unique_username") as! String)
self.resultsImageFiles.append(catchobject.objectForKey("profile_picture") as? PFFile)
self.resultsDetailsArray.append(catchobject.objectForKey("details") as! String)
self.resultsDetailsImageFiles.append(catchobject.objectForKey("detailsImage") as? PFFile)
self.resultsTitle.append(catchobject.objectForKey("title") as! String)
self.resultsObjectID.append(catchobject.objectId!)
}
dispatch_async(dispatch_get_main_queue()) {
self.resultsTable.reloadData()
}
self.loadEmptyLabel(self.resultsTable)
}
self.refresher.endRefreshing()
}
break
case 1:
...
break
default:
break
}
}
func loadEmptyLabel(tableView: UITableView) {
let emptyLabel = UILabel(frame: CGRectMake(0, 0, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height))
emptyLabel.textAlignment = NSTextAlignment.Center
emptyLabel.textColor = UIColor.blackColor()
emptyLabel.text = "No matched result found."
tableView.backgroundView = emptyLabel
tableView.separatorStyle = UITableViewCellSeparatorStyle.None
var resultCount = Int()
if tableView == resultsTable {
resultCount = resultsNameArray.count
} else {
resultCount = resultsTitleArray.count
}
if resultCount == 0 {
tableView.reloadData()
emptyLabel.hidden = false
} else {
emptyLabel.hidden = true
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var numRow: Int = 0
switch(segmentedControl.selectedSegmentIndex){
case 0:
numRow = resultsNameArray.count
break
case 1:
numRow = resultsTitleArray.count
break
default:
break
}
return numRow
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if tableView == resultsTable {
let cell:favoritedTableViewCell = resultsTable.dequeueReusableCellWithIdentifier("Cell") as! favoritedTableViewCell
cell.profileLbl.text = self.resultsNameArray[indexPath.row]
cell.messageTxt.text = self.resultsDetailsArray[indexPath.row]
cell.priceLabel.text = "\(self.personOrderTypeArray[indexPath.row]) \(self.personQuantityArray[indexPath.row]) for $\(self.personPriceArray[indexPath.row])"
cell.titleLabel.text = self.resultsTitle[indexPath.row]
if resultsImageFiles[indexPath.row] != nil {
resultsImageFiles[indexPath.row]!.getDataInBackgroundWithBlock { (imageData:NSData?, error:NSError?) -> Void in
if error == nil{
let image = UIImage(data: imageData!)
cell.imgView.image = image
}
}
} else {
cell.imgView.image = UIImage(named: "Profile Picture")
}
if resultsDetailsImageFiles[indexPath.row] != nil{
resultsDetailsImageFiles[indexPath.row]?.getDataInBackgroundWithBlock({ (imageData:NSData?, error:NSError?) -> Void in
if error == nil{
let image = UIImage(data: imageData!)
cell.detailsImg.image = image
}
})
} else {
cell.detailsImg.image = UIImage(named: "Profile Picture")
}
return cell
} else {
....
}
}
Your numberOfRowsInSection function returns one of two array lengths based on segmentedControl.selectedSegmentIndex, whereas cellForRowAtIndexPath indexes the arrays based on the tableView being displayed. This doesn't look right, especially given your referencing `` which doesn't appear to be populated anywhere - should it just be resultsTitle?.
Also, you're calling self.resultsTable.reloadData() from a background thread. This is bad - it must be called from the main thread using:
dispatch_async(dispatch_get_main_queue()) {
self.resultsTable.reloadData()
}
Nevertheless, it's not clear why you've got this inside the loop either.