How to make a customisable navigation bar title? Swift 4 - swift4.1

I have a textfield on my firstViewController and what I want to happen is: once the user entered a text and goes to the SecondViewController it will become the navigation bar title.
I'm new at programming and I was hoping someone could help me.

First way to navigate creating object of view controller and navigate through navigation controller
let second = storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
second.title = textTitle.text!
navigationController?.pushViewController(second, animated: true)
Second way to navigate from storyboard
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == "segueIdentifier" else { return }
let destination = segue.destination as? SecondViewController
destination?.strTitle = textTitle.text!
}
In second view controller add this
var strTitle : String?
override func viewDidLoad() {
super.viewDidLoad()
self.title = strTitle ?? "default string"
}

Can you try #ChanWarde way but present the view controller rather than using segues ?
let second = storyboard?.instantiateViewController(withIdentifier:
"SecondViewController") as! SecondViewController
second.title = textTitle.text!
present(second, animated: true, completion: nil)
Or try:
let second = storyboard?.instantiateViewController(withIdentifier:
"SecondViewController") as! SecondViewController
second.title = textTitle.text!
DispatchQueue.main.async(execute: {
UIApplication.shared.keyWindow?.rootViewController = second()
self.dismiss(animated: true, completion: nil)
})

Related

Action when user click on the delete button on the keyboard in SwiftUI

I try to run a function when the user click on the delete button on the keyboard when he try to modify a Textfield.
How can I do that ?
Yes it is possible, however it requires subclassing UITextField and creating your own UIViewRepresentable
This answer is based on the fantastic work done by Costantino Pistagna in his medium article but we need to do a little more work.
Firstly we need to create our subclass of UITextField, this should also conform to the UITextFieldDelegate protocol.
class WrappableTextField: UITextField, UITextFieldDelegate {
var textFieldChangedHandler: ((String)->Void)?
var onCommitHandler: (()->Void)?
var deleteHandler: (() -> Void)?
override func deleteBackward() {
super.deleteBackward()
deleteHandler?()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let nextField = textField.superview?.superview?.viewWithTag(textField.tag + 1) as? UITextField {
nextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return false
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let currentValue = textField.text as NSString? {
let proposedValue = currentValue.replacingCharacters(in: range, with: string)
textFieldChangedHandler?(proposedValue as String)
}
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
onCommitHandler?()
}
}
Because we are creating our own implementation of a TextField we need three functions that we can use for callbacks.
textFieldChangeHandler this will be called when the text property updates and allows us to change the state value associated with our Textfield.
onCommitHandler this will be called when we have finished editing our TextField
deleteHandler this will be called when we perform he delete action.
The code above shows how these are used. The part that you are particularly interested in is the override func deleteBackward(), by overriding this we are able to hook into when the delete button is pressed and perform an action on it. Depending on your use case, you may want the deleteHandler to be called before you call the super.
Next we need to create our UIViewRepresentable.
struct MyTextField: UIViewRepresentable {
private let tmpView = WrappableTextField()
//var exposed to SwiftUI object init
var tag:Int = 0
var placeholder:String?
var changeHandler:((String)->Void)?
var onCommitHandler:(()->Void)?
var deleteHandler: (()->Void)?
func makeUIView(context: UIViewRepresentableContext<MyTextField>) -> WrappableTextField {
tmpView.tag = tag
tmpView.delegate = tmpView
tmpView.placeholder = placeholder
tmpView.onCommitHandler = onCommitHandler
tmpView.textFieldChangedHandler = changeHandler
tmpView.deleteHandler = deleteHandler
return tmpView
}
func updateUIView(_ uiView: WrappableTextField, context: UIViewRepresentableContext<MyTextField>) {
uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
uiView.setContentHuggingPriority(.defaultLow, for: .horizontal)
}
}
This is where we create our SwiftUI version of our WrappableTextField. We create our WrappableTextField and its properties. In the makeUIView function we assign these properties. Finally in the updateUIView we set the content hugging properties, but you may choose not to do that, it really depends on your use case.
Finally we can create a small working example.
struct ContentView: View {
#State var text = ""
var body: some View {
MyTextField(tag: 0, placeholder: "Enter your name here", changeHandler: { text in
// update the state's value of text
self.text = text
}, onCommitHandler: {
// do something when the editing finishes
print("Editing ended")
}, deleteHandler: {
// do something here when you press delete
print("Delete pressed")
})
}
}

App crash when delete cell Item with custom Button (CoreData)

When i'm delete cell from my CollectionView app is crashed (CoreData).
I'm use custom button in CollectionView.
var cardItems = [NSManagedObject]()
cell.MenuButton.layer.setValue(indexPath.row, forKey: "index")
cell.MenuButton.addTarget(self, action: #selector(MenuCell), for: UIControl.Event.touchUpInside)
Error
EDIT (It's work)
let i: Int = (sender.layer.value(forKey: "index")) as! Int
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedContext = appDelegate.persistentContainer.viewContext
let indexPath = IndexPath(item: i, section: 0)
let itemToDelete = self.cardItems[indexPath.item]
self.cardItems.remove(at: indexPath.item)
managedContext.delete(itemToDelete)
appDelegate.saveContext()
self.collectionView.reloadData()
First of all, it's not a good way to identify the click button. Instead, you can set button tag as indexPath.row like below:
menuButton.tag = indexPath.row
cell.MenuButton.layer.setValue(indexPath.row, forKey: "index")
Also improve your coding skills see below:
cell.MenuButton.addTarget(self, action: #selector(onClickMenuButton(_:)), for: .touchUpInside)
Implement this selector as:
let i: Int = (sender.layer.value(forKey: "index")) as! Int
func onClickMenuButton(_ sender: UIButton) {
let task = self.cardItems[sender.tag]
if let managedContext = task.managedObjectContext {
managedContext.delete(task)
do {
try managedContext.save()
self.cardItems.remove(at: sender.tag)
let indexPath = IndexPath(row: i, section: 0)
self.collectionView.deleteItems(at: [indexPath])
} catch {
print("Failed to delete")
}
}
}
Your array indexPaths does not have any value. It is because there is no collection view items which are in selected state
You can select first collection view item programmatically as follows :
let indexPath = collectionView.indexPathsForSelectedItems?.first ?? IndexPath(item: 0, section: 0)
self.collectionView.selectItem(at: indexPath, animated: false, scrollPosition: UICollectionView.ScrollPosition.centeredHorizontally)

Swift 4 - How do I test for TextView content did change?

Swift 4, iOS 11 - I have a UITextView that is pre-populated with text but I want users to be able to save any changes they make to the content there. I also have a Save button in the navigation bar and I would like to disable it until the user actually changes the text in the TextView.
I know how to test for empty but I don't know how to test for when the text has been edited. How do I modify the following to test for changes to the content of TextView?
#IBAction func textEditingChanged(_ sender: UITextView) {
updateSaveButtonState()
}
func updateSaveButtonState() {
let descriptionText = descriptionTextView.text ?? ""
saveButton.isEnabled = !descriptionText.isEmpty
}
We'll to use it a dynamic way and not only in single place, i tried to make it easier to implement around the whole app, subclassing the UITextView is one of the only ways we got here #holex has suggested isEdited boolean flag and it gave me an idea, Thanks to that.
Here is the steps to implement it:
First of all set the defaultText of the textView and set the target of the method that will be called when the textView will be edited, so you can customize what ever you want.
#IBOutlet weak var saveButton: UIBarButtonItem!
#IBOutlet weak var textView: SBTextView!{
didSet{
textView.defaultText = "Hello"
textView.setTarget = (selector:#selector(self.updateSaveButtonState),target:self)
}
}
Lets say you'll setup the saveButton in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
// setup save button action
saveButton.action = #selector(saveAction(_:))
saveButton.target = self
self.updateSaveButtonState()
}
And last is your save action and the selector to update the view using isEdited flag.
//MARK:- Actions
#objc private func updateSaveButtonState(){
// has not been changed keep save button disabled
if self.textView.isEdited == false{
self.saveButton.isEnabled = false
self.saveButton.tintColor = .gray
}else {
// text has been changed enable save button
self.saveButton.isEnabled = true
self.saveButton.tintColor = nil // will reset the color to default
}
}
#objc private func saveAction(_ saveButton:UIBarButtonItem){
self.textView.updateDefaultText()
}
TextView Custom Class:
//
// SBTextView.swift
//
//
// Created by Saad Albasha on 11/17/17.
// Copyright © 2017 AaoIi. All rights reserved.
//
import UIKit
class SBTextView: UITextView,UITextViewDelegate {
var isEdited = false
private var selector : Selector?
private var target : UIViewController?
var setTarget: (selector:Selector?,target:UIViewController?) {
get{
return (selector,target)
}
set(newVal) {
selector = newVal.0
target = newVal.1
}
}
var textViewDefaultText = ""
var defaultText: String {
get {
return textViewDefaultText
}
set(newVal) {
textViewDefaultText = newVal
self.text = newVal
self.isEdited = false
}
}
//MARK:- Life Cycle
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
self.setupTextview()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupTextview()
}
private func setupTextview(){
// setup textview
self.text = textViewDefaultText
self.delegate = self
}
func updateDefaultText(){
self.defaultText = self.text!
// update save button state
target!.perform(self.selector, with: nil, with: nil)
}
//MARK:- Delegate
internal func textViewDidChange(_ textView: UITextView) {
if textViewDefaultText != textView.text! {
isEdited = true
}else {
isEdited = false
}
// update save button state
target!.perform(self.selector, with: nil, with: nil)
}
}
I hope this helps.

How do i add the Menu controller to any UiViewController , other than the rootview?

I am trying just to display the floating Menu on a separate view controller that is not the rootview ? Normally i just add it to the rootview controller.
let menuController = AppMenuController(rootViewController: toolbarController)
let navigationController = AppNavigationDrawerController(rootViewController: menuController, leftViewController: leftViewController,rightViewController: rightViewController)
let statusController = AppStatusBarController(rootViewController: navigationController)
window = UIWindow(frame: Screen.bounds)
window!.rootViewController = statusController
window!.makeKeyAndVisible()
let main: MainViewController = {
return UIStoryboard.viewController(identifier: "MainViewController") as! MainViewController}()
let newView = AppMenuController(rootViewController: main)
self.present(allergyView, animated: false, completion: nil)
Add the Menu as a property of the UIViewController. For example:
class ViewController: UIViewController {
fileprivate let menu = Menu()
fileprivate let baseSize = CGSize(width: 64, height: 64)
open override func viewDidLoad() {
super.viewDidLoad() {
prepareMenu()
}
}
extension MenuController {
fileprivate func prepareMenu() {
menu.zPosition = 1000
menu.baseSize = baseSize
view.layout(menu)
.size(baseSize)
.bottom(24)
.right(24)
}
}
In cases where you would not want to use the Material controllers with root view controllers, you should be able to look at the setup code in those controllers to see the code necessary to add the component they manage, for example MenuController.
All the best :)

Swift - Add MKAnnotationView To MKMapView

I'm trying to add MKAnnotationView to MKMapView but I can't do it… Can anyone help me?
Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
var latitude:CLLocationDegrees = locationManager.location.coordinate.latitude
var longitude:CLLocationDegrees = locationManager.location.coordinate.longitude
var homeLati: CLLocationDegrees = 40.01540192
var homeLong: CLLocationDegrees = 20.87901079
var latDelta:CLLocationDegrees = 0.01
var longDelta:CLLocationDegrees = 0.01
var theSpan:MKCoordinateSpan = MKCoordinateSpanMake(latDelta, longDelta)
var myHome:CLLocationCoordinate2D = CLLocationCoordinate2DMake(homeLati, homeLong)
var myLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude, longitude)
var theRegion:MKCoordinateRegion = MKCoordinateRegionMake(myLocation, theSpan)
self.theMapView.setRegion(theRegion, animated: true)
self.theMapView.mapType = MKMapType.Hybrid
self.theMapView.showsUserLocation = true
///Red Pin
var myHomePin = MKPointAnnotation()
myHomePin.coordinate = myHome
myHomePin.title = "Home"
myHomePin.subtitle = "Bogdan's home"
self.theMapView.addAnnotation(myHomePin)
var anView:MKAnnotationView = MKAnnotationView()
anView.annotation = myHomePin
anView.image = UIImage(named:"xaxas")
anView.canShowCallout = true
anView.enabled = true
}
You need to implement the viewForAnnotation delegate method and return an MKAnnotationView from there.
Here's an example:
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if (annotation is MKUserLocation) {
//if annotation is not an MKPointAnnotation (eg. MKUserLocation),
//return nil so map draws default view for it (eg. blue dot)...
return nil
}
let reuseId = "test"
var anView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if anView == nil {
anView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
anView.image = UIImage(named:"xaxas")
anView.canShowCallout = true
}
else {
//we are re-using a view, update its annotation reference...
anView.annotation = annotation
}
return anView
}
Remember you need to create a plain MKAnnotationView when you want to use your own custom image. The MKPinAnnotationView should only be used for the standard pin annotations.
Also note you should not try to access locationManager.location immediately after calling startUpdatingLocation. It may not be ready yet.
Use the didUpdateLocations delegate method.
You also need to call requestAlwaysAuthorization/requestWhenInUseAuthorization (see Location Services not working in iOS 8).
You have to show annotation, then you have to add the show annotation method:
let annotation = MKPointAnnotation()
mapVew.showAnnotations([annotation], animated: true)
annotation is an instance of MKPointAnnotation.
1- your map view should delegate itself to your view controller
2- you should implement
func mapView(aMapView: MKMapView!, viewForAnnotation annotation: CustomMapPinAnnotation!) -> MKAnnotationView!
this function is called from every annotation added to your Map
3- subclass your annotations to identify them in the viewForAnnotation method
4- in viewForAnnotation, add
if annotation.isKindOfClass(CustomMapPinAnnotation)
{
// change the image here
var pinView = aMapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? YourSubclassedAnnotation
pinView!.image = UIImage(contentsOfFile: "xyz")
}

Resources