UITextField doesn't end editing in a UITableViewCell in Swift 4 - ios

I am still new to swift and I would ask you for advice. Thank you in advance and sorry for my bad English.
My goal is:
User tap edit button in the table's row. UITextField appears instead cell. After entering value and pressing Return key UITextField disappears again and cell is recalculated.
editButton pressed -> hide priceCell & show UITextField & show keyboard & start editing/entering value (blinking cursor) -> stop editing/entering value execute by pressing Return key -> hide UITextField & shows priceCell & save entered value into array & reload edited row
I use this answer as starting blueprint.
I would like to also use .decimalPad keyboard to easier entering numeric value and limit user to use only numbers (and decimal point), but this exclude use Return key as stop editing, am I right?
I found this possible solution, but it seems to me complex for my problem...
my ViewController:
import UIKit
class PortfolioViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, PortfolioCellDelegate {
let getData = GetData()
...
override func viewDidLoad() {
super.viewDidLoad()
cellTableView.delegate = self
cellTableView.dataSource = self
cellTableView.register(UINib(nibName: "PortfolioCell", bundle: nil), forCellReuseIdentifier: "portfolioCell")
self.currencyControl.selectedSegmentIndex = MyVariables.currencyControlSelected
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let coinCell = tableView.dequeueReusableCell(withIdentifier: "portfolioCell", for: indexPath) as! PortfolioCell
...
coinCell.delegate = self
return coinCell
}
...
func portfolioButtonPressed(coinCell: PortfolioCell) {
let indexPath = self.cellTableView.indexPathForRow(at: coinCell.center)!
let selectedCell = cellTableView.cellForRow(at: indexPath) as! PortfolioCell
selectedCell.priceCell.isHidden = true
selectedCell.textCell.isHidden = false
selectedCell.textCell.delegate = self
func textFieldDidEndEditing(textField: UITextField) {
let owned: Double = Double(textField.text!)!
if owned >= 0 {
MyVariables.dataArray[indexPath.row].ownedCell = owned
} else {
MyVariables.dataArray[indexPath.row].ownedCell = 0.00
}
selectedCell.priceCell.isHidden = false
selectedCell.textCell.isHidden = true
self.cellTableView.reloadData()
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
selectedCell.textCell.resignFirstResponder()
return true
}
}
...
}
my custom cell:
import UIKit
protocol PortfolioCellDelegate {
func portfolioButtonPressed(coinCell: PortfolioCell)
}
class PortfolioCell: UITableViewCell {
var delegate: PortfolioCellDelegate?
...
#IBAction func editCellPressed(_ sender: UIButton) {
delegate?.portfolioButtonPressed(coinCell: self)
}
...
}
For now when button is pressed proper UITextField shows, but don't dismiss after Return key is pressed.
Or should I change it completely and use tap gestures?

To end editing in any kind of scrollView, simply use this
cellTableView.keyboardDismissMode = .onDrag
or
cellTableView.keyboardDismissMode = .interactive
It will hide keyboard when you interact with the tableView

For number keypad you can add toolbar as a textField's inputAccessoryView. On toolbar add cancel button to dismiss keyboard.

There is two way to go:
1.) Delegate
2.) IQKeyboardManager
1.)
Use UITextFieldDelegate
There is one particular callback named "textFieldShouldEndEditing"
In this method, return true.
2.)
User the IQKeyboardManager one liner library. This library manages all the TextFields and scrollviews automatically. You activate it with one line in AppDelegate so it's easy to use.
https://github.com/hackiftekhar/IQKeyboardManager

Working but not as sleek as want it to be and also I was not capable to make IQKeyboardManager works so I did use inputAccessoryView.
custom cell:
import UIKit
protocol PortfolioCellDelegate {
func portfolioButtonPressed(didSelect coinCell: PortfolioCell)
func portfolioButtonPressed(coinCell:PortfolioCell, editingChangedInTextCell newValue:String)
}
class PortfolioCell: UITableViewCell, UITextFieldDelegate {
var delegate: PortfolioCellDelegate?
...
#IBAction func editCellPressed(_ sender: UIButton) {
textCell.becomeFirstResponder()
delegate?.portfolioButtonPressed(didSelect: self)
}
#IBAction func textCellValueChanged(_ sender: UITextField) {
if (sender.text?.isEmpty)! {
delegate?.portfolioButtonPressed(coinCell: self, editingChangedInTextCell: "XXX")
} else {
let text = sender.text
delegate?.portfolioButtonPressed(coinCell: self, editingChangedInTextCell: text!)
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.textCell.delegate = self
let flexiableSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.done, target: self, action: #selector(self.doneButtonAction))
let toolBar:UIToolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: 35))
toolBar.barTintColor = UIColor(red:0.15, green:0.69, blue:0.75, alpha:1.0)
toolBar.tintColor = UIColor(red:0.93, green:0.93, blue:0.93, alpha:1.0)
toolBar.setItems([flexiableSpace, doneButton], animated: false)
textCell.inputAccessoryView = toolBar
textCell.keyboardType = UIKeyboardType.decimalPad
}
#objc func doneButtonAction() {
textCell.endEditing(true)
}
...
}
ViewController:
import UIKit
class PortfolioViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, PortfolioCellDelegate {
let getData = GetData()
...
override func viewDidLoad() {
super.viewDidLoad()
cellTableView.delegate = self
cellTableView.dataSource = self
cellTableView.register(UINib(nibName: "PortfolioCell", bundle: nil), forCellReuseIdentifier: "portfolioCell")
self.currencyControl.selectedSegmentIndex = MyVariables.currencyControlSelected
getData.delegate = self
timeStampLabel.text = MyVariables.timeStamp
}
override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.cellTableView.reloadData()
self.currencyControl.selectedSegmentIndex = MyVariables.currencyControlSelected
self.timeStampLabel.text = MyVariables.timeStamp
}
//MARK: - tableView
/***************************************************************/
...
func portfolioButtonPressed(coinCell: PortfolioCell, editingChangedInTextCell newValue: String) {
let indexPath = self.cellTableView.indexPathForRow(at: coinCell.center)!
let selectedCell = cellTableView.cellForRow(at: indexPath) as! PortfolioCell
selectedCell.priceCell.isHidden = false
selectedCell.textCell.isHidden = true
if newValue != "XXX" {
let owned: Double = Double(newValue)!
MyVariables.dataArray[indexPath.row].ownedCell = owned
}
selectedCell.priceCell.isHidden = false
selectedCell.textCell.isHidden = true
self.cellTableView.reloadRows(at: [indexPath], with: .automatic)
}
func portfolioButtonPressed(didSelect coinCell: PortfolioCell) {
let indexPath = self.cellTableView.indexPathForRow(at: coinCell.center)!
let selectedCell = cellTableView.cellForRow(at: indexPath) as! PortfolioCell
selectedCell.priceCell.isHidden = true
selectedCell.textCell.isHidden = false
}
...
}

It's easy: You should select that table view cell, then enable User Interaction Enabled in the attribute inspector.

Related

Activating a search controller whose navigation item is not at the top of the stack | Swift

The error:
[Assert] Surprise! Activating a search controller whose navigation item is not at the top of the stack. This case needs examination in UIKit. items = (null),
search hosting item = <UINavigationItem: 0x1068473a0> title='PARTS' style=navigator leftBarButtonItems=0x282bfb8d0 rightBarButtonItems=0x282bfb890 searchController=0x110024400 hidesSearchBarWhenScrolling
Why am I getting this error and how do I fix it? This question is similar to another post, but there was only one response to it and the response was not detailed at all (therefore not helpful).
import UIKit
import SPStorkController
struct Part {
var title: String?
var location: String?
var selected: Bool? = false
}
class InspectorViewController: UIViewController, UINavigationControllerDelegate, UITableViewDataSource, UITableViewDelegate, UISearchResultsUpdating, UISearchBarDelegate {
private let initials = InspectorPartsList.getInitials() // model
private var parts = InspectorPartsList.getDamageUnrelatedParts() // model
var filteredParts: [Part] = [] // model
var searching = false
var searchController = UISearchController(searchResultsController: nil)
lazy var searchBar: UISearchBar = UISearchBar()
var isSearchBarEmpty: Bool {
return searchController.searchBar.text?.isEmpty ?? true
}
var navBar = UINavigationBar()
let inspectorTableView = UITableView() // tableView
var darkTheme = Bool()
override func viewDidLoad() {
super.viewDidLoad()
// setup the navigation bar
setupNavBar()
// add the table view
setupInspectorTableView()
// add the search controller to the navigation bar
setupSearchController()
}
func setupNavBar() {
navBar = UINavigationBar(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 100))
view.addSubview(navBar)
let navItem = UINavigationItem(title: "PARTS")
let doneItem = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(self.addBtnTapped))
let cancelItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: nil, action: #selector(self.cancelBtnTapped))
navItem.rightBarButtonItem = doneItem
navItem.leftBarButtonItem = cancelItem
navItem.searchController = searchController
navBar.setItems([navItem], animated: false)
}
#objc func cancelBtnTapped() {
// dismiss the storkView
SPStorkController.dismissWithConfirmation(controller: self, completion: nil)
}
#objc func addBtnTapped() {
// get all of the selected rows
// Update the InspectionData model with the selected items... this will allow us to update the InspectionTableView in the other view
// create an empty array for the selected parts
var selectedParts = [Part]()
// loop through every selected index and append it to the selectedParts array
for part in parts {
if part.selected! {
selectedParts.append(part)
}
}
// update the InspectionData model
if !selectedParts.isEmpty { // not empty
InspectionData.sharedInstance.partsData?.append(contentsOf: selectedParts)
// update the inspectionTableView
updateInspectionTableView()
}
// dismiss the storkView
SPStorkController.dismissWithConfirmation(controller: self, completion: nil)
}
func cancelAddPart() {
// dismiss the storkView
SPStorkController.dismissWithConfirmation(controller: self, completion: nil)
}
private func setupInspectorTableView() {
// set the data source
inspectorTableView.dataSource = self
// set the delegate
inspectorTableView.delegate = self
// add tableview to main view
view.addSubview(inspectorTableView)
// set constraints for tableview
inspectorTableView.translatesAutoresizingMaskIntoConstraints = false
// inspectorTableView.topAnchor.constraint(equalTo: fakeNavBar.bottomAnchor).isActive = true
inspectorTableView.topAnchor.constraint(equalTo: navBar.bottomAnchor).isActive = true
inspectorTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
inspectorTableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
inspectorTableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
// allow multiple selection
inspectorTableView.allowsMultipleSelection = true
inspectorTableView.allowsMultipleSelectionDuringEditing = true
// register the inspectorCell
inspectorTableView.register(CheckableTableViewCell.self, forCellReuseIdentifier: "inspectorCell")
}
func setupSearchController() {
// add the bar
searchController.searchResultsUpdater = self
searchController.searchBar.delegate = self
searchController.hidesNavigationBarDuringPresentation = false
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search by part name or location"
definesPresentationContext = true
searchController.searchBar.sizeToFit()
self.inspectorTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return filteredParts.count
} else {
return parts.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let inspectorCell = tableView.dequeueReusableCell(withIdentifier: "inspectorCell", for: indexPath)
var content = inspectorCell.defaultContentConfiguration()
var part = Part()
if searching {
// showing the filteredParts array
part = filteredParts[indexPath.row]
if filteredParts[indexPath.row].selected! {
// selected - show checkmark
inspectorCell.accessoryType = .checkmark
} else {
// not selected
inspectorCell.accessoryType = .none
}
} else {
// showing the parts array
part = parts[indexPath.row]
if part.selected! {
// cell selected - show checkmark
inspectorCell.accessoryType = .checkmark
} else {
// not selected
inspectorCell.accessoryType = .none
}
}
content.text = part.title
content.secondaryText = part.location
inspectorCell.contentConfiguration = content
return inspectorCell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Note: When you select or unselect a part in the filteredParts array, you must also do so in the parts array
if searching { // using filteredParts array
if filteredParts[indexPath.row].selected! { // selected
filteredParts[indexPath.row].selected = false // unselect the part
// search the parts array for the part by both the title and location, so we for sure get the correct part (there could be parts with identical titles with different locations)
if let part = parts.enumerated().first(where: { $0.element.title == filteredParts[indexPath.row].title && $0.element.location == filteredParts[indexPath.row].location}) { // exact part (with same title & location) found
parts[part.offset].selected = false // unselect the part
}
} else { // not selected
filteredParts[indexPath.row].selected = true // select the part
if let part = parts.enumerated().first(where: { $0.element.title == filteredParts[indexPath.row].title && $0.element.location == filteredParts[indexPath.row].location}) { // exact part (with same title & location) found
parts[part.offset].selected = true // select the part
}
}
} else { // using parts array
if parts[indexPath.row].selected! { // selected
parts[indexPath.row].selected = false // unselect the part
} else { // not selected
parts[indexPath.row].selected = true // select the part
}
}
inspectorTableView.reloadRows(at: [indexPath], with: .none) // reload the tableView
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredParts = parts.filter { ($0.title?.lowercased().prefix(searchText.count))! == searchText.lowercased() }
searching = true
inspectorTableView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searching = false
searchBar.text = ""
inspectorTableView.reloadData()
}
func updateSearchResults(for searchController: UISearchController) {
}
private func updateInspectionTableView() {
NotificationCenter.default.post(name: NSNotification.Name("updateInspectionTable"), object: nil)
}
}
// CHECKABLE UITABLEVIEWCELL
class CheckableTableViewCell: UITableViewCell {
}

How can I reuse part of code (recognizer, toolbar) applied to a textview?

I have a class called ThemeVC which has a textview (connected with an IBoutlet) and functionalities applied to it (it has a recognizer that detects the tapped words).
My goal here is that I would like to extract that piece of functionality, and put it maybe in its own class or create a delegate so I could reuse that functionality on other textviews.
Anyone knows how?
I pasted my code below.
(HERE comments, are functions that should be called from any view controller)
import UIKit
class ThemeVC: UIViewController, UITextViewDelegate, UINavigationControllerDelegate {
#IBOutlet weak var themeTextView: UITextView!
var tB = UIBarButtonItem()
// Move away from ThemeVC ... ->
var selectionDict = [String:Int]()
var viewTagCount = Int()
var tap = UIGestureRecognizer()
var firstTimeGrouped = false
// -> ... Move away from ThemeVC
override func viewDidLoad() {
super.viewDidLoad()
themeTextView.delegate = self
loadbuttons ()
//HERE
addTagSelectorToolBar ()
}
func loadbuttons () {
tB = UIBarButtonItem(image: UIImage(systemName: "hand.point.up.left"), style: .plain, target: self, action: #selector(getTag(sender:)))
navigationItem.rightBarButtonItems = [tB]
}
#objc func getTag(sender: AnyObject) {
themeTextView.resignFirstResponder()
//HERE
startTagSelection()
}
}
// Move away from ThemeVC ... ->
extension ThemeVC {
func startTagSelection () {
navigationController?.setToolbarHidden(false, animated: false)
tap.isEnabled = true
tB.isEnabled = false
themeTextView.isEditable = false
themeTextView.isSelectable = false
}
}
extension ThemeVC {
#objc func doneTagSelection(){
navigationController?.setToolbarHidden(true, animated: false)
tap.isEnabled = false
tB.isEnabled = true
themeTextView.isEditable = true
themeTextView.isSelectable = true
firstTimeGrouped = false
}
}
extension ThemeVC {
func addTagSelectorToolBar (){
addTappedTagRecognizer()
tap.isEnabled = false
let done = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTagSelection))
let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
toolbarItems = [spacer, done]
}
}
extension ThemeVC {
func addTappedTagRecognizer () {
tap = UITapGestureRecognizer(target: self, action: #selector(tapResponse(recognizer:)))
tap.delegate = self as? UIGestureRecognizerDelegate
themeTextView.addGestureRecognizer(tap)
}
#objc private func tapResponse(recognizer: UITapGestureRecognizer) {
let location: CGPoint = recognizer.location(in: themeTextView)
let position: CGPoint = CGPoint(x:location.x, y:location.y)
let tapPosition: UITextPosition? = themeTextView.closestPosition(to:position)
if tapPosition != nil {
let textRange: UITextRange? = themeTextView.tokenizer.rangeEnclosingPosition(tapPosition!, with: UITextGranularity.word, inDirection: UITextDirection(rawValue: 1))
if textRange != nil
{
let tappedWord: String? = themeTextView.text(in:textRange!)
print(tappedWord ?? "Unable to get word")
}
}
}
}
// ... -> Move away from ThemeVC
How to test my code:
Create a new project with a storyboard
On the left hand side rename viewcontroller with themeVC, and replace
its code with the code I gave.
On the storyboard, embed the controller in a navigation controller, on right side, change in identity inspector class from view controller to themeVC
add a textview and link it to the IBoutlet
Looking at the parts you want to move away from ThemeVC, I would have to say not everything should be moved away from ThemeVC.
For example, you marked startTagSelection as something you want to move away, but you reference the navigationController which belongs to the view controller so it should ideally not be the responsibility of your UITextView to update your UINavigationBar.
So the two ideas discussed in the comments was using SubClasses and Protocols.
Protocols was the suggestion of Ptit Xav so I will show one way that could be used, Ptit Xav could add an answer if something else was in mind.
I start with creating a protocol
// Name the protocol as you see appropriate
// I add #objc so it can be accessible from Storyboard
// This will be used to `hand over` responsibility of
// a certain action / event
#objc
protocol CustomTextViewTagDelegate: class {
func customTextViewDidStartSelection(_ textView: CustomTextView)
func customTextViewDidFinishSelection(_ textView: CustomTextView)
}
Next I subclass a UITextView to add my own customization
#IBDesignable
class CustomTextView: UITextView {
var selectionDict = [String:Int]()
var viewTagCount = Int()
var tap = UIGestureRecognizer()
var firstTimeGrouped = false
// Name it as you wish
// #IBInspectable added for storyboard accessibility
// You could also make it an IBOutlet if your prefer
// that interaction
#IBInspectable
weak var tagDelegate: CustomTextViewTagDelegate?
func startTagSelection () {
// Remove the commented lines as this should the responsibility of
// the view controller, manage in the view controller using the delegate
// navigationController?.setToolbarHidden(false, animated: false)
// tB.isEnabled = false
tap.isEnabled = true
isEditable = false
isSelectable = false
// Hand over responsibility of this action back whatever
// has subscribed as the delegate to implement anything else
// for this action
tagDelegate?.customTextViewDidStartSelection(self)
}
func addTappedTagRecognizer () {
tap = UITapGestureRecognizer(target: self,
action: #selector(tapResponse(recognizer:)))
tap.delegate = self as? UIGestureRecognizerDelegate
addGestureRecognizer(tap)
}
#objc private func tapResponse(recognizer: UITapGestureRecognizer) {
let location: CGPoint = recognizer.location(in: self)
let position: CGPoint = CGPoint(x:location.x,
y: location.y)
let tapPosition: UITextPosition? = closestPosition(to:position)
if tapPosition != nil {
let textRange: UITextRange? = tokenizer.rangeEnclosingPosition(tapPosition!,
with: UITextGranularity.word,
inDirection: UITextDirection(rawValue: 1))
if textRange != nil
{
let tappedWord: String? = text(in:textRange!)
print(tappedWord ?? "Unable to get word")
}
}
}
#objc func doneTagSelection() {
// This is not the text view's responsibility, manage in the
// view controller using the delegate
// navigationController?.setToolbarHidden(true, animated: false)
// tB.isEnabled = true
tap.isEnabled = false
isEditable = true
isSelectable = true
firstTimeGrouped = false
// Hand over responsibility of this action back whatever
// has subscribed as the delegate to implement anything else
// for this action
tagDelegate?.customTextViewDidFinishSelection(self)
}
}
And finally use it like so
class ThemeVC: UIViewController {
// Change UITextView to CustomTextView
#IBOutlet weak var themeTextView: CustomTextView!
var tB = UIBarButtonItem()
// If you do not set up the delegate in your
// storyboard, you need to it in your code
// call this function from didLoad or something
// if needed
private func configureTextView() {
themeTextView.tagDelegate = self
}
// All your other implementation
}
extension ThemeVC: CustomTextViewTagDelegate {
func customTextViewDidStartSelection(_ textView: CustomTextView) {
navigationController?.setToolbarHidden(false,
animated: false)
tB.isEnabled = false
}
func customTextViewDidFinishSelection(_ textView: CustomTextView) {
navigationController?.setToolbarHidden(true,
animated: false)
tB.isEnabled = true
}
}
I did not add addTagSelectorToolBar as part of the CustomTextView implementation as this is not a good candidate to be part of that module as all of its code is related to the view controller so i don't recommend making a part of the CustomTextView implementation.

How to pass array of data between UIViewControllers from #objc function

I have a Child UICollectionViewController where I have an array of images.
When I delete any photo I want to send back that array of updated images to Parent UIViewController.
Also in Child controller I have a programatically view which is called when I click on any image to expand it. When the image is expanded the user can click on a Delete button to delete photos from that array.
My array is updated correctly after delete but I can't manage to send it back to parent for some reasons.
I tried to send it back using Delegates and Protocols.
Here is my code for child controller:
protocol ListImagesDelegate {
func receiveImagesUpdated(data: [String]?)
}
class ListImagesVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
// Properties
var receivedImagesPath: [String]? = []
var fullscreenImageView = UIImageView()
var indexOfSelectedImage = 0
var imagesAfterDelete: [String]? = []
var delegate: ListImagesDefectDelegate?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("imagesAfterDelete: \(imagesAfterDelete ?? [])") // I'm getting the right number of images in this array.
delegate?.receiveImagesUpdated(data: imagesAfterDelete)
}
...
...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Click on photo: \(indexPath.item + 1)")
if let imagePath = receivedImagesPath?[indexPath.item] {
guard let selectedImage = loadImageFromDiskWith(fileName: imagePath) else {return}
setupFullscreenImageView(image: selectedImage)
indexOfSelectedImage = indexPath.item
}
}
private func setupFullscreenImageView(image: UIImage){
fullscreenImageView = UIImageView(image: image)
fullscreenImageView.frame = UIScreen.main.bounds
fullscreenImageView.backgroundColor = .black
fullscreenImageView.contentMode = .scaleAspectFit
fullscreenImageView.isUserInteractionEnabled = true
self.view.addSubview(fullscreenImageView)
self.navigationController?.isNavigationBarHidden = true
self.tabBarController?.tabBar.isHidden = true
let deleteButton = UIButton(frame: CGRect(x: fullscreenImageView.bounds.maxX - 50, y: fullscreenImageView.bounds.maxY - 75, width: 30, height: 40))
deleteButton.autoresizingMask = [.flexibleLeftMargin, .flexibleBottomMargin]
deleteButton.backgroundColor = .black
deleteButton.setImage(UIImage(named: "trashIcon"), for: .normal)
deleteButton.addTarget(self, action: #selector(deleteButtonTapped), for: .touchUpInside)
fullscreenImageView.addSubview(deleteButton)
}
#objc func deleteButtonTapped(button: UIButton!) {
print("Delete button tapped")
receivedImagesPath?.remove(at: indexOfSelectedImage)
imagesAfterDelete = receivedImagesPath
collectionView.reloadData()
self.navigationController?.isNavigationBarHidden = false
self.tabBarController?.tabBar.isHidden = false
fullscreenImageView.removeFromSuperview()
}
Here is the Parent controller:
var updatedImages: [String]? = []
...
...
extension NewAlbumVC: ListImagesDelegate {
func receiveImagesUpdated(data: [String]?) {
print("New array: \(data ?? [])") // This print is never called.
updatedImages = data
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToImages" {
let listImagesVC = segue.destination as! ListImagesVC
listImagesVC.delegate = self
}
}
}
I want to specify that my child controller have set a Storyboard ID ("ListImagesID") and also a segue identifier from parent to child ("goToImages"). Can cause this any conflict ?
Thanks if you read this.
It appears that the delegate is nil here
delegate?.receiveImagesUpdated(data: imagesAfterDelete)
For this
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
to trigger you must have
self.performSegue(withIdentifier:"goToImages",sender:nil)
Edit: This
let listImagesDefectVC = storyboard?.instantiateViewController(withIdentifier: "ListImagesDefectID") as! ListImagesDefectVC
listImagesDefectVC.receivedImagesPath = imagesPath
navigationController?.pushViewController(listImagesDefectVC, animated: true)
doesn't trigger prepareForSegue , so add
listImagesDefectV.delegate = self
So finally
Solution 1 :
#objc func tapOnImageView() {
let listImagesDefectVC = storyboard?.instantiateViewController(withIdentifier: "ListImagesDefectID") as! ListImagesDefectVC
listImagesDefectVC.receivedImagesPath = imagesPath
listImagesDefectVC.delegate = self
navigationController?.pushViewController(listImagesDefectVC, animated: true)
}
Solution 2 :
#objc func tapOnImageView() {
self.performSegue(withIdentifier:"goToImages",sender:nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToImages" {
let listImagesVC = segue.destination as! ListImagesVC
listImagesVC.receivedImagesPath = imagesPath
listImagesVC.delegate = self
}
}

Interaction between DUXPreFlightStatusWidget and DUXPreflightChecklistController is not working as advertised

According to https://developer.dji.com/api-reference/ios-uilib-api/Widgets/PreFlightStatusWidget.html:
"Tapping on status text will toggle between show and hide DUXPreflightChecklistController."
When I tap on the status text in the widget, the DUXPreflightChecklistController is not shown. Also, if I manually show the DUXPreflightChecklistController, there is a close button in the top right corner of the panel but tapping it does not hide the panel.
What is the proper way to configure this panel?
I'm using DJISDK 4.7.1 and DJIUXSDK 4.7.1 with Swift and iOS 12/xCode 10.0.
To provide a bit more detail, I do not want to use the Default Layout but I am using DUXStatusBarViewController. That is embedded in a UIView across the top of my app. I cannot find any properties for that controller that would allow me to hook it up to my instance of DUXPreflightChecklistController, which is also embedded in a UIView.
For: DUXPreflightChecklistController
I'd just solved that
var preflightChecklistController: DUXPreflightChecklistController!
weak var preFlightTableView: UITableView!
private var compassItemIndex: Int = -1
private var storageItemIndex: Int = -1
override func viewDidLoad() {
super.viewDidLoad()
preflightChecklistController = DUXPreflightChecklistController()
addChild(preflightChecklistController)
}
func renderChecklist() {
if let checklistVC = preflightChecklistController {
for subview in checklistVC.view.subviews {
if subview.isKind(of: UITableView.self) {
if let tableView = subview as? UITableView {
self.view.addSubview(tableView)
preFlightTableView = tableView
}
}
}
guard let checklistManager = checklistVC.checklistManager else { return }
let itemList = checklistManager.preFlightChecklistItems
for (index, item) in itemList.enumerated() {
if let _ = item as? DUXStorageCapacityChecklistItem {
storageItemIndex = index
}
if let _ = item as? DUXCompassChecklistItem {
compassItemIndex = index
}
}
preFlightTableView.reloadData()
checklistManager.startCheckingList()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
renderChecklist()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard preFlightTableView != nil else { return }
if let compassCell = preFlightTableView.cellForRow(at: IndexPath(item: compassItemIndex, section: 0)) {
for view in compassCell.subviews {
if let button = view as? UIButton, button.titleLabel?.text == "Calibrate" {
button.addTarget(self, action: #selector(doActionForSpecifiedBTN(sender:)), for: .touchUpInside)
break
}
}
}
if let storageCell = preFlightTableView.cellForRow(at: IndexPath(item: storageItemIndex, section: 0)) {
for view in storageCell.subviews {
if let button = view as? UIButton, button.titleLabel?.text == "Format" {
button.addTarget(self, action: #selector(doActionForSpecifiedBTN(sender:)), for: .touchUpInside)
break
}
}
}
}
#objc func doActionForSpecifiedBTN(sender: UIButton) {
guard let btnTitle = sender.titleLabel else { return }
switch btnTitle.text {
case "Calibrate":
// your func goes here
case "Format":
// your func goes here
default:
break
}
}

didSet property value only appears in print()

INITIAL GOAL:
Have a view with a list of cells positioned vertically displaying some information. As soon as the user clicks on a cell to show a new view with more information.
THE ROAD SO FAR (curry on my wayward son!):
I created 2 view controllers: ViewController (subclassing UICollectionViewController, UICollectionViewDelegateFlowLayout) and DetailViewController (subclassing UIViewController).
I created a Cell that the ViewController uses to generate the collection view and a DetailView that the DetailViewController uses
I created a struct named Detail as a custom data type which provides storage of data using properties (ex. name, surname, address, etc.)
The struct:
struct Detail: Decodable {
let name: String?
let surname: String?
let address: String?
let description: String?
}
I use the following data for testing (after the testing is done I will get this data from an API call). I placed it inside ViewController:
let details: [Detail] = [Detail(name: "Chris", surname: "Doe", address: "Neverland 31", description: "This is a description about Chris Doe"), Detail(name: "Tony", surname: "Cross", address: "Galaxy Road 1", description: "This is a description about Tony Cross")]
To create the cells using the information above and the method:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
And also:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! Cell
As the method requires us to return a UICollectionViewCell, I first send the associated information to Cell by doing the following:
cell.details = details[indexPath.item]
return cell
Inside the Cell I created the following property using didSet to help me retrieve the information:
var details: Detail? {
didSet {
guard let details = details else { return }
guard let name = details.name else { return }
....
....
}
As you can understand using the information coming from ViewController I dynamically constructed each cell.
All were good at this point.
Then I tried to show a detailed view when clicking on a cell. To do this I followed the same practice inside the method:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let detailView = DetailView()
detailView.details = details[indexPath.item]
let detailViewController = DetailViewController()
detailViewController.modalTransitionStyle = .coverVertical
self.present(detailViewController, animated: true, completion: nil)
}
Again, in the DetailView I use the same approach to get the data associated with the selected item. This way I can have access to the data of the cell the user selects, as shown below:
import UIKit
class DetailView: UIView {
var dismissDetailViewAction: (() -> Void)?
var details: Detail? {
didSet {
// get details
guard let details = details else { return }
guard let name = details.name else { return }
guard let surname = details.surname else { return }
guard let address = details.address else { return }
guard let description = details.description else { return }
// print description and it shows in the console but not in the view
print(description)
let attributedTextDescription = NSMutableAttributedString(string: description, attributes: [NSAttributedStringKey.font: UIFont.FontBook.AvertaRegular.of(size: 20), NSAttributedStringKey.foregroundColor: UIColor.white])
briefDescription.attributedText = attributedTextDescription
briefDescription.textAlignment = .center
briefDescription.textColor = .white
briefDescription.numberOfLines = 0
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not yet been implemented")
}
fileprivate func setupView() {
setupDescriptionText()
setupCloseButton()
}
let briefDescription: UITextView = {
let text = UITextView()
text.textColor = .red
return text
}()
let closeButton: UIButton = {
let button = UIButton(title: "Close", font: UIFont.FontBook.AvertaRegular.of(size: 18), textColor: .white, cornerRadius: 5)
button.backgroundColor = .black
button.addTarget(self, action: #selector(closeDetailView), for: .touchUpInside)
return button
}()
fileprivate func setupDescriptionText() {
self.addSubview(briefDescription)
briefDescription.translatesAutoresizingMaskIntoConstraints = false
briefDescription.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 5).isActive = true
briefDescription.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -5).isActive = true
briefDescription.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
briefDescription.heightAnchor.constraint(equalToConstant: 300).isActive = true
}
fileprivate func setupCloseButton() {
self.addSubview(closeButton)
closeButton.translatesAutoresizingMaskIntoConstraints = false
closeButton.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
closeButton.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
closeButton.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 40).isActive = true
closeButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -40).isActive = true
closeButton.heightAnchor.constraint(equalToConstant: 60).isActive = true
}
#objc func closeDetailView() {
dismissDetailViewAction?()
}
}
So, what I actually do is to design the static part of the view outside didSet, and what is dynamic part inside didSet. This works with the cells of collectionView.
I use the DetailViewController to display the DetailView and dismiss itself when the user clicks on the "Close" button.
import UIKit
class DetailViewController: UIViewController {
// reference DetailView view
var detailView: DetailView!
override func viewDidLoad() {
super.viewDidLoad()
// setup view elements
setupView()
}
fileprivate func setupView() {
let mainView = DetailView(frame: self.view.frame)
self.detailView = mainView
self.view.addSubview(detailView)
self.homeDetailView.dismissDetailViewAction = dismissDetailView
// pin view
self.detailView.translatesAutoresizingMaskIntoConstraints = false
self.detailView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.detailView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.detailView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
self.detailView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
}
fileprivate func dismissDetailView() {
// dismiss current (DetailViewController) controller
self.dismiss(animated: true, completion: nil)
}
}
The reason I did this is that I like to keep my ViewControllers as clean as possible (Massive View Controller, not my thing).
THE PROBLEM
The whole thing is built without any problem, but when I click on a cell to go to the DetailView no information is displayed.
THE WEIRD PART
Inside the DetailView --> didSet, when I use print(name), it works just fine (you see the correct name inside console). But when I try to use that value inside the view it will not be displayed.
And I know that my DetailView is just fine since if I use hardcoded values in it, it works (you see the correct result).
Any advise why this is not working properly?
PS: I am building the whole thing programmatically. No storyboards involved.
Thanks in advance and sorry for the lost post.
As was mentioned, your detailView is not referenced inside detailViewController. Instead, you create another instance of DetailView inside DetailViewController but this one has no Detail in it.
The console message was called from inside your detailView, but inside detailViewController is another instance that did not call this message, because its Detail is set to nil by default.
To be short, to fix that you should simply do the following changes:
import UIKit
class DetailViewController: UIViewController {
var detail: Detail!
private lazy var detailView: DetailView = {
let mainView = DetailView(frame: self.view.frame)
mainView.details = detail
return mainView
}
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
fileprivate func setupView() {
self.view.addSubview(detailView)
self.homeDetailView.dismissDetailViewAction = dismissDetailView
// pin view
self.detailView.translatesAutoresizingMaskIntoConstraints = false
self.detailView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.detailView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.detailView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
self.detailView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
}
fileprivate func dismissDetailView() {
// dismiss current (DetailViewController) controller
self.dismiss(animated: true, completion: nil)
}
}
And inside your collectionView(...) func:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let detailViewController = DetailViewController()
detailViewController.detail = details[indexPath.item]
detailViewController.modalTransitionStyle = .coverVertical
self.present(detailViewController, animated: true, completion: nil)
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let detailView = DetailView()
detailView.details = details[indexPath.item]
let detailViewController = DetailViewController()
detailViewController.modalTransitionStyle = .coverVertical
self.present(detailViewController, animated: true, completion: nil)
}
You make a DetailView here, pass it your details... and then do nothing with it.
Normally DetailView would be a property of the DetailViewController and you'd pass the details to the view controller, which would display it.
What's happening here is that you're creating, configuring and throwing away a DetailView, when you probably should be using the one that DetailViewController owns, or should own.

Resources