UITableView Not Showing Reorder Controls - swift

When I use trailingSwipeActionsConfigurationForRowAt my TableView will show the delete and reorder options, however when selecting reorder nothing happens. I think I have all of the correct methods and am calling setEditing; is there anything else I'm missing? Thanks!
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
}
func setupTableView() {
tableView.frame = self.view.frame
tableView.dataSource = self
tableView.delegate = self
tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
tableView.dragInteractionEnabled = true
self.view.addSubview(tableView)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 8
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.backgroundColor = .gray
cell.showsReorderControl = true
return cell
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
self.tableView.setEditing(editing, animated: animated)
}
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .normal, title: "delete") { (action, view, completion) in
tableView.reloadData()
completion(true)
}
let reorderAction = UIContextualAction(style: .normal, title: "reorder") { (action, view, completion) in
tableView.setEditing(true, animated: true)
completion(true)
}
return UISwipeActionsConfiguration(actions: [deleteAction, reorderAction])
}
}
class CustomCell: UITableViewCell {
}
Result after swiping:
After selecting reorder:

A few observations:
You are not going to get the reorder controls if you do not implement tableView(_:moveRowAt:to:), e.g., assuming you had a model which was an array called objects, you could do the following:
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let object = objects.remove(at: sourceIndexPath.row)
objects.insert(object, at: destinationIndexPath.row)
}
The trailingSwipeActionsConfigurationForRowAt is probably not the right place to put a “reorder” command. Part of the reason is that once the table view is in edit mode and you tap on the ⛔️, the trailing actions show up, and “reorder” does not make sense in that context. E.g., here I am tapping on ⛔️ and I see the confusing actions.
I would suggest only adding “delete” as the trailing action. That way, you (a) get only “delete” if you tap on ⛔️ in isEditing mode, but also (b) get the stand-alone swipe action, too.
You cannot initiate isEditing from the trailing swipe actions (and, as discussed above, I do not think you want to, anyway). So, if you do not have “reorder” in the trailing swipe actions, you need some other method to enter edit mode. E.g., above, I added an “edit” button to the navigation bar that toggles isEditing:
#IBAction func didTapEdit(_ sender: Any) {
tableView.isEditing.toggle()
}
Then, you can keep the swipe to delete functionality, but when you tap on edit button, you have the tap on ⛔️ to delete functionality (plus the handles for reordering because we added tableView(_:moveRowAt:to:) as outlined in step one, above):
Another way to achieve reordering is to just allow drag and drop within the table view where you can long-press on a row and then drag it:
This is enabled by setting dragInteractionEnabled and dropDelegate:
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
return formatter
}()
private var objects: [Foo] = ...
override func viewDidLoad() {
super.viewDidLoad()
...
tableView.dragInteractionEnabled = true
tableView.dropDelegate = self
}
}
// MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource { ... }
// MARK: - UITableViewDelegate
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "delete") { [weak self] action, view, completion in
self?.objects.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .middle)
completion(true)
}
return UISwipeActionsConfiguration(actions: [deleteAction])
}
// This is used if table view is in `isEditing` mode and by `UITableViewDropDelegate`
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let object = objects.remove(at: sourceIndexPath.row)
objects.insert(object, at: destinationIndexPath.row)
}
}
// MARK: - UITableViewDropDelegate
extension ViewController: UITableViewDropDelegate {
func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
guard
session.items.count == 1, // Accept only one drag item ...
tableView.hasActiveDrag // ... from within this table view
else {
return UITableViewDropProposal(operation: .cancel)
}
return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
guard let destinationIndexPath = coordinator.destinationIndexPath else { return }
for item in coordinator.items {
if let sourceIndexPath = item.sourceIndexPath {
DispatchQueue.main.async {
tableView.moveRow(at: sourceIndexPath, to: destinationIndexPath)
}
}
}
}
}
Clearly, if you were going to enable drag from this app to others, you would add UITableViewDragDelegate conformance here, and make your model objects conform to NSItemProviderReading and NSItemProviderWriting. But the above should be sufficient for dragging and dropping to reorder within a UITableView.

Related

Update Specific Button Image in UITableView Cell

I am trying to add an action to my "like" button. So that when the user taps the heart UIButton in a cell, the heart in the cell they tapped updates to a pink heart showing that they liked it. But instead it likes the heart they tapped and another random heart in a different cell that they did not interact with. I have been on this all day and any help would be grateful. For Example, if I like/tap my heart UIButton the buttons image I tapped updates, but when I scroll down another random heart updates from that same first cell button tap.
Also When I scroll and the cell leaves view and scroll back up the image returns back to unlike and other like buttons become liked.
Keep a data model for your buttons state
Try with the below code
struct TableModel {
var isLiked: Bool
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var dataSource: [TableModel] = []
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
overrideUserInterfaceStyle = .light
dataSource = Array(repeating: TableModel(isLiked: false), count: 20)
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.showsVerticalScrollIndicator = false
self.tableView.reloadData()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
dataSource.count
}
#objc func buttonSelected(_ sender: UIButton) {
dataSource[sender.tag].isLiked = !dataSource[sender.tag].isLiked
let indexPath = IndexPath(row: sender.tag, section: 0)
tableView.reloadRows(at: [indexPath], with: .automatic)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.likeBtn.tag = indexPath.row
cell.likeBtn.addTarget(self, action: #selector(buttonSelected(_:)), for: .touchUpInside)
let isLiked = dataSource[indexPath.row].isLiked
if isLiked {
cell.likeBtn.setImage(UIImage(named: "liked"), for: UIControl.State.normal)
} else {
//set unlike image
}
return cell
}
}
Currently, you have a hardcoded number of rows, but anyway you will need to have a data source with data models. When you press the button, you have to save the state of the button of a specific row. I would recommend you create a model first.
Here I provided an easy (but flexible enough) way how to do this. I haven't debugged it, but it should work and you can see the idea. I hope this would be helpful.
Create Cell Model
struct CellViewModel {
let title: String
var isLiked: Bool
// Add other properties you need for the cell, image, etc.
}
Update cell class
It's better to handle top action right in the cell class. To handle this action on the controller you can closure or delegate like I did.
// Create a delegate protocol
protocol TableViewCellDelegate: AnyObject {
func didSelectLikeButton(isLiked: Bool, forCell cell: TableViewCell)
}
class TableViewCell: UITableViewCell {
// add a delegate property
weak var delegate: TableViewCellDelegate?
#IBOutlet var titleTxt: UILabel!
#IBOutlet var likeBtn: UIButton!
//...
override func awakeFromNib() {
super.awakeFromNib()
// You can add target here or an action in the Storyboard/Xib
likeBtn.addTarget(self, action: #selector(likeButtonSelected), for: .touchUpInside)
}
/// Method to update state of the cell
func update(with model: CellViewModel) {
titleTxt.text = model.title
likeBtn.isSelected = model.isLiked
// To use `isSelected` you need to set different images for normal state and for selected state
}
#objc private func likeButtonSelected(_ sender: UIButton) {
sender.isSelected.toggle()
delegate?.didSelectLikeButton(isLiked: sender.isSelected, forCell: self)
}
}
Add an array of models and use it
This is an updated class of ViewController with usage of models.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// Provide a list of all models (cells)
private var cellModels: [CellViewModel] = [
CellViewModel(title: "Title 1", isLiked: false),
CellViewModel(title: "Title 2", isLiked: true),
CellViewModel(title: "Title 3", isLiked: false)
]
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
overrideUserInterfaceStyle = .light
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.showsVerticalScrollIndicator = false
self.tableView.reloadData()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// return count of cell models
return cellModels.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
let model = cellModels[indexPath.row]
// call a single method to update the cell UI
cell.update(with: model)
// and you need to set delegate in order to handle the like button selection
cell.delegate = self
return cell
}
}
extension ViewController: TableViewCellDelegate {
func didSelectLikeButton(isLiked: Bool, forCell cell: TableViewCell) {
// get an indexPath of the cell which call this method
guard let indexPath = tableView.indexPath(for: cell) else {
return
}
// get the model by row
var model = cellModels[indexPath.row]
// save the updated state of the button into the cell model
model.isLiked = isLiked
// and set the model back to the array, since we use struct
cellModels[indexPath.row] = model
}
}

Gap between navigation bar and first UITableViewCell

I have a UITableViewController with a search controller embedded in the navigation bar.
When the view shows in a modal, there is a gap between the first row and the navigation bar.
here is my implementation. Some code has been redacted for confidentiality. But anything regarding view controller setup should still be there:
import Foundation
class SearchViewController: UITableViewController {
fileprivate lazy var searchController: UISearchController = {
let searchController = UISearchController(searchResultsController: nil)
searchController.dimsBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = false
searchController.searchBar.showsCancelButton = true
searchController.searchBar.delegate = self
return searchController
}()
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
navigationItem.titleView = searchController.searchBar
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.navigationBar.tintColor = BrandColor.appleDarkGray
}
}
extension ExploreSearchViewController: UISearchBarDelegate {
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
presentingViewController?.dismiss(animated: true, completion: nil)
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if let searchText = searchBar.text {
tableView.refreshControl?.beginRefreshing()
searchedText = searchText
}
}
}
// MARK: - UITableView Methods
extension ExploreSearchViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchResultsProvider.searchResults.count
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard searchResultsProvider.canLoadNextPage else { return }
// handle paging here...
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard searchResultsProvider.searchResults.count > indexPath.row else { return UITableViewCell() }
// Setup search result cell here...
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Handle cell did tap here...
}
#objc private func refresh() {
searchResultsProvider.refreshResults()
}
// MARK - Table View Helpers
private func setupTableView() {
registerCells()
self.tableView.backgroundColor = .white
self.tableView.contentInsetAdjustmentBehavior = .always
self.tableView.refreshControl = UIRefreshControl()
self.tableView.refreshControl?.addTarget(self, action: #selector(refresh), for: .valueChanged)
self.tableView.useDefaultPageLoadingIndicator()
self.tableView.setBackgroundViewIfEmpty(message: Self.searchInitialMessage, remedyButton: nil)
}
private func registerCells() {
//Register Table View Cells here...
}
}
Has anyone seen this kind of issue before? I can't move the top edge insets up because then the refresh control will be hidden behind the navigation bar.
Anyone seen this behaviour before? If so, how did you solve it?
The issue was caused by the table view style was set to .grouped on the previous view controller.
Setting the style to .plain solved this issue.
I believe the reason grouped style causes the blank space is because its adding an empty section header above the cells.

How to programmatically segue from searchbar to display screen

So I have recently coded a search bar programmatically using tableview and navigation control. I am now having trouble finding information on how to segue the search bar after the user clicks on an item in the search bar.
I have tried using a view controller but that has not worked. I think my best bet is to do it programmatically.
EDIT There is no function yet in this code to implement a display screen. Im wondering what code is needed (im a very new developer) to get to another screen after clicking an element in the search bar. anything helps!!***
import UIKit
class SearchTableViewController: UITableViewController, UISearchBarDelegate {
let searchBar = UISearchBar()
let tableData = ["Boston University", "Boston College", "Northeastern University", "Suffolk University", "American University", "Harvard University", "Massachusetts Institute of Technology", "Tufts University", "Berklee College of Music", "Emerson College"]
//variables added for search function
var filteredArray = [String()]
var shouldShowSearchResults = false
override func viewDidLoad() {
super.viewDidLoad()
createSearcherBar()
}
func createSearcherBar() {
searchBar.showsBookmarkButton = false
searchBar.placeholder = "Search College"
searchBar.delegate = self
self.navigationItem.titleView = searchBar
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredArray = tableData.filter({ (names: String) -> Bool in
return names.range(of: searchText, options: .caseInsensitive) != nil
})
if searchText != "" {
shouldShowSearchResults = true
self.tableView.reloadData()
}
else{
shouldShowSearchResults = false
self.tableView.reloadData()
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if shouldShowSearchResults {
return filteredArray.count
}
else{
return tableData.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "basicCell", for: indexPath)
if shouldShowSearchResults {
cell.textLabel!.text = filteredArray[indexPath.row]
return cell
}
else{
cell.textLabel!.text = tableData[indexPath.row]
return cell
}
}
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
searchBar.endEditing(true)
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
shouldShowSearchResults = true
searchBar.endEditing(true)
self.tableView.reloadData()
}
/*
// Override to support conditional editing of the table view.
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
*/
/*
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// Delete the row from the data source
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
}
*/
/*
// Override to support conditional rearranging of the table view.
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the item to be re-orderable.
return true
}
*/
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
Hopefully I can get a user to click on one of the options in a search bar, and it brings them to a different page.
First of all your method textDidChange is unnecessarily expensive because you are always filtering the array even if the search text is empty. This is more efficient
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
shouldShowSearchResults = false
filteredArray.removeAll() // good practice to release memory when the search is finished
} else {
filteredArray = tableData.filter{ $0.range(of: searchText, options: .caseInsensitive) != nil }
shouldShowSearchResults = true
}
self.tableView.reloadData()
}
Second of all the declaration of filteredArray is slightly wrong. The parentheses must be outside the brackets. Your syntax declares a string array containing one empty string.
var filteredArray = [String]()
To answer your question implement didSelectRowAt and add the same shouldShowSearchResults logic to distinguish the arrays. Call performSegue and pass the string as sender
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = shouldShowSearchResults ? filteredArray[indexPath.row] : tableData[indexPath.row]
performSegue(withIdentifier: "MyIdentifier", sender: item)
}
and get it in prepare(for segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == "MyIdentifier" else { return }
let destinationController = segue.destination as! MyViewController
let item = sender as! String
...

How do I inject or add data to my table as it isn't working?

I just cannot seem to update data in Swift! I am trying to build a radio player app for a friends radio station so when a song changes I need to update the playlist viewcontroller.
The data from my Main View Controler is a instance of a struct. I know there is data being generated and it is passed to the table but for whatever reason the array isn't updating. I am sure it is something simple.
I have tried directly injecting the data with a call, using a protocol and using a function. Using the protocol and function I can see the passed data via print statement.
class PlaylistVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
//Mark: Variables ~~~~~~~~~~~~###########################################
var sourceDatas = [PlaylistData]()
//Mark: View Containers ~~~~~############################################
private let bg = GradientBG()
private let closeButton = UIButton()
let playlistTable = UITableView()
//Mark: Overrides ~~~~~~~~~~~~###########################################
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
playlistTable.delegate = self
playlistTable.dataSource = self
layoutUI()
setupTable()
setupClose()
}
//Mark: objc func's ~~~~~~~~~~~###########################################
#IBAction func handleXButton() {
dismiss(animated: true, completion: nil)
}
func handleMoreInfo(_ playlist: PlaylistData) {
let vc = SongPopUp()
vc.buildLables(playlist)
vc.modalPresentationStyle = .overCurrentContext
self.present(vc, animated: true, completion: nil )
}
//##################################################################
//Mark: Pass Data ##################################################
//Mark: Not Working!! ##############################################
//##################################################################
func handlePlaylist(_ with: PlaylistData) {
print(with)
sourceDatas.append(with)
//sourceDatas.insert(with, at: 0)
playlistTable.reloadData()
}
//Mark: Table view ################################################
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sourceDatas.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = UITableViewCell(style: .subtitle, reuseIdentifier: "myCell")
cell.backgroundColor = .clear
cell.selectionStyle = .none
tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
if let text = cell.textLabel {
components.layoutHeadingLable(sender: text, title: sourceDatas[indexPath.row].heading, size: 20)
}
if let dtext = cell.detailTextLabel {
components.layoutSubheadingLable(sender: dtext, title: sourceDatas[indexPath.row].subheading, size: 14)
}
if sourceDatas[indexPath.item].hasStoreLink {
cell.accessoryType = .detailButton
cell.tintColor = .white
}
return cell
}
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
let dataToPass = self.sourceDatas[indexPath.row]
print("Extended~~~~~~~~~~~~~~~~~~~~")
print(dataToPass)
handleMoreInfo(sourceDatas[indexPath.row])
}
//Mark: Setup Table ~~~~~~~~~~~~~~###########################################
func setupTable() {
playlistTable.backgroundColor = .clear
playlistTable.separatorStyle = .singleLine
playlistTable.rowHeight = 45
playlistTable.register(UITableViewCell.self, forCellReuseIdentifier: "myCell")
}
......
I think it's something wrong with your cellForRowAt
Try to make it simple first, like:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! UITableViewCell
cell.textLabel?.text = sourceDatas[indexPath.row]
return cell
}
See whether you can find the added object. Then dive into the detail settings of your cell.

How to reload a collectionview when I tap on back?

My collectionview did not reload when I tap back from the tableview where I the list reorder.
I have read a few topics but I don't know what's wrong. Can anybody find it?
And can I the speak part of the code place in an apart swift file and include this ViewController. So yes, how? Because I use that in more ViewControllers.
Below is my code.
Thank you very much.
import UIKit
import AVFoundation
class CollectionViewController: UICollectionViewController {
#IBOutlet var soundBoard: UICollectionView!
var list = ["January","February","March","April","May","June", "July","August","September","October","November", "December"]
var bgColor = [""]
var buttonOn: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
let blockItems = UserDefaults.standard.object(forKey:"soundboard")
if(blockItems != nil) {
list = blockItems as! [String]
}
let itemSize = UIScreen.main.bounds.width/2 - 2
let itemHeight = itemSize / 2
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsetsMake(3, 0, 3, 0)
layout.itemSize = CGSize(width: itemSize, height: itemHeight)
layout.minimumInteritemSpacing = 3
layout.minimumLineSpacing = 3
soundBoard.collectionViewLayout = layout
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.collectionView!.reloadData()
}
// MARK: - Collection View
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return list.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> MyCollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCollectionViewCell
cell.cellTitle.text = list[indexPath.row]
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
Speak(currentTitle: list[indexPath.row])
}
// MARK: - Speak function
let synth = AVSpeechSynthesizer()
var myUtterance = AVSpeechUtterance(string: "")
func Speak(currentTitle: String) {
if !synth.isSpeaking {
// Controleert volume
let volume = AVAudioSession.sharedInstance().outputVolume
if volume < 0.5 {
MuteButton()
}
// Spreekt de tekst uit
let myUtterance = AVSpeechUtterance(string: currentTitle)
myUtterance.rate = 0.4
myUtterance.volume = 1
if let theVoice = UserDefaults.standard.object(forKey:"voice") {
myUtterance.voice = AVSpeechSynthesisVoice(language: theVoice as? String)
}
synth.speak(myUtterance)
}
}
func MuteButton() {
let alertController = UIAlertController(title: "Check your volume", message:
"Please check your volume or mute button.", preferredStyle: UIAlertControllerStyle.alert)
alertController.addAction(UIAlertAction(title: "Close", style: UIAlertActionStyle.default,handler: nil))
self.present(alertController, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: -- Save UserDefaults
func save() {
UserDefaults.standard.set(self.list, forKey:"soundboard")
UserDefaults.standard.synchronize()
self.collectionView?.reloadData()
}
// MARK: -- Add new sound item
#IBAction func addNew(_ sender: Any) {
let alertController = UIAlertController(title: "Create a new sound", message: "", preferredStyle: UIAlertControllerStyle.alert)
alertController.addTextField { (textField: UITextField) in
textField.keyboardAppearance = .dark
textField.keyboardType = .default
textField.autocorrectionType = .default
textField.placeholder = "Type something here"
textField.clearButtonMode = .whileEditing
}
alertController.addAction(UIAlertAction(title: "Create", style: .default) { [weak alertController] _ in
if let alertController = alertController {
let loginTextField = alertController.textFields![0] as UITextField
self.list.append( loginTextField.text!)
self.save()
}
})
alertController.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.default,handler: nil))
self.present(alertController, animated: true, completion: nil)
}
}
Two things are happening here. First you appear to have two different collection views in CollectionViewController; collectionView which is inherited from the superclass and soundboard which you declare. In viewWillAppear you tell collectionView to reload. If this is not the one that is hooked up in IB nothing will work.
The second issue is that while in soundboardTableViewController you update the user default with the correct data you never read it after the view loads in CollectionViewController.
You should move this:
let blockItems = UserDefaults.standard.object(forKey:"soundboard")
if(blockItems != nil) {
list = blockItems as! [String]
}
into viewWillAppear before your call to reload the collection view and then verify you are telling the right collection view to reload.
I see a few things that could be going wrong here. It would help to see the code in the other view controller however.
First are you sure that you're reordering your model array properly? Remember in swift an array is a value type. Meaning that if you pass the array to another view you're passing a copy of that array and not the array in your collection view controller. It may be a better idea to wrap your model array in an object which you can pass a reference to. More on that here.
Also it's better to tie a reload to a change in your model instead of the view lifecycle. To do that with your current code you could do something like this:
var list = ["January","February","March","April","May","June", "July","August","September","October","November", "December"] {
didSet {
self.collectionView?.reloadData()
// or
self.soundBoard.reloadData()
// I'm confused about which collection view you're using/trying to reload.
}
}
Please let me know if you have any questions. I'll keep an eye out and can edit my answer if needed.
This is the code of the other ViewController where you can reorder the rows and can tap on < Back.
import UIKit
class soundboardTableViewController: UITableViewController {
var list = ["January","February","March","April","May","June", "July","August","September","October","November", "December"]
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = self.editButtonItem
let blockItems = UserDefaults.standard.object(forKey:"soundboard")
if(blockItems != nil) {
list = blockItems as! [String]
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return list.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = list[indexPath.row]
return cell
}
// Override to support conditional editing of the table view.
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
/* Override to support editing the table view.*/
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
list.remove(at: indexPath.row)
UserDefaults.standard.set(self.list, forKey:"soundboard")
UserDefaults.standard.synchronize()
tableView.reloadData()
}
}
// Override to support rearranging the table view.
override func tableView(_ tableView: UITableView, moveRowAt indexPath: IndexPath, to: IndexPath) {
/*let itemToMove = list[indexPath.row]
list.remove(at: indexPath.row)
list.insert(itemToMove, at: indexPath.row)
UserDefaults.standard.set(self.list, forKey:"soundboard")
UserDefaults.standard.synchronize()*/
let itemToMove = list[indexPath.row]
list.remove(at: indexPath.row)
list.insert(itemToMove, at: to.row)
UserDefaults.standard.set(self.list, forKey:"soundboard")
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if(self.isEditing) {
}
}
// Override to support conditional rearranging of the table view.
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the item to be re-orderable.
return true
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
}