Swift menu not showing on right navigation button click - swift

New to Swift. I have a simple UI: put a UINavigationBar on top of a UIWebView, and a the right bar button item to have an action that shows a menu, to allow the use choose different pages to show in the web view.
Show the view controller looks like:
class ViewController: UIViewController {
#IBOutlet weak var webView: UIWebView!
#IBOutlet weak var menu: UIBarMenuItem!
#IBOutlet weak var viewnav: UIView!
override func viewDidLoad() {
super.viewDidLoad();
let url = URL(string:"about:blank")
let req = URLRequest(url:url!)
webView.loadRequest(req)
}
#obj func dummy(){
}
#IBAction func MenuShow(sender: UIBarButtonItem){
let menu = UIMenuController.shared
viewnav.becomeFirstResponder()
menu.setTargetRect(viewnav.frame, in:viewnav)
let dummy = UIMenuItem(title:"Dummy", action: #selector(dummy))
menu.menuItems = [dummy]
menu.setMenuVisible(true, animated: true)
//for test only; should move to menu item actions
let url = URL(string:"https://www.apple.com")
let req = URLRequest(url:url!)
webView.loadRequest(req)
}
}
(I have connected the web view, the bar button to the UI object; for viewnav I tried adding a new dummy view in Main.storyboard or using the existing navigation bar, both have the same result)
The resulting app shows the empty page and when I hit the menu button, jumps into Apple's home page, so the above code runs as expected. But the menu didn't show up, so what is wrong for the code above?
(there are a few other simular questions like this, but they didn't seem to solving the problem)

This answer gives the solution:
override var canBecomeFirstResponder: Bool {
return true
}
And add this line to the viewDidLoad method
view.becomeFirstResponder()
Full version:
class ViewController: UIViewController {
#IBOutlet weak var webView: UIWebView!
#IBOutlet weak var menuButton: UIBarMenuItem!
override func viewDidLoad() {
super.viewDidLoad();
let url = URL(string:"about:blank")
let req = URLRequest(url:url!)
webView.loadRequest(req)
view.becomeFirstResponder()
let menu = UIMenuController.shared
let dummy = UIMenuItem(title:"Dummy", action: #selector(dummy))
menu.menuItems = [dummy]
}
override var canBecomeFirstResponder: Bool {
return true
}
#obj func dummy(){
let url = URL(string:"https://www.apple.com")
let req = URLRequest(url:url!)
webView.loadRequest(req)
menu.setMenuVisible(true, animated: false)
}
#IBAction func MenuShow(sender: UIBarButtonItem){
let menu = UIMenuController.shared
let bv = menuButton.value(forKey: "view") as? UIView
menu.setTargetRect(bv!.frame, in:view)
menu.setMenuVisible(true, animated: true)
}
}

Related

Presenting view controller from detached view controller is discouraged. Keeping User logged in issue

I'm trying to have the user move to automatically go to the Home Screen and not have to log in again. Basically, to remember the user. I used User Defaults to save the user login info and put the listener for the key in the viewDidLoad of the first login page. I used an if statement to switch the view controllers but it doesn't work and prints (Presenting view controller from detached view controller is discouraged).
LoginViewController:
import UIKit
import FirebaseAuth
import AVKit
class LoginViewController: UIViewController {
var videoPlayer:AVPlayer?
var videoPlayerLayer:AVPlayerLayer?
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var Back: UIButton!
#IBOutlet weak var passwordtextField: UITextField!
#IBOutlet weak var loginButton: UIButton!
#IBOutlet weak var errorLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
setupElements()
}
func dismissKeyboard() {
//Causes the view (or one of its embedded text fields) to resign the first responder status.
view.endEditing(true)
}
func setupElements(){
errorLabel.alpha = 0
Utilities.styleTextField(emailTextField)
Utilities.styleTextField(passwordtextField)
Utilities.styleFilledButton(loginButton)
}
func validateFields() -> String?
{
//make sure fields are filled
if emailTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" || passwordtextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == ""
{
return "Please fill all fields"
}
return nil
}
#IBAction func loginTapped(_ sender: Any) {
//creates a clean version of the text field
let email = emailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let password = passwordtextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let error = validateFields()
//sign in user
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
UserDefaults.standard.set(Auth.auth().currentUser!.uid, forKey: "user_uid_key")
UserDefaults.standard.synchronize()
if error != nil{
self.errorLabel.text = "Invalid Username/Password try again."
self.errorLabel.alpha = 1
}
else{
let homeViewController = self.storyboard?.instantiateViewController(identifier: Constants.StoryBoard.homeViewController) as?
HomeViewController
self.view.window?.rootViewController = homeViewController
self.view.window?.makeKeyAndVisible()
}
}
//make sure all fields are filled
}
override func viewWillAppear(_ animated: Bool) {
setUpVideo()
}
func setUpVideo(){
//Get path to resource bundle
let bundlePath = Bundle.main.path(forResource: "IMG_7211 2", ofType: "mov")
guard bundlePath != nil else{
return
}
//create the url from it
let url = URL(fileURLWithPath: bundlePath!)
//Create The video Player item
let item = AVPlayerItem(url: url)
//create the player
videoPlayer = AVPlayer(playerItem: item)
//create the layer
videoPlayerLayer = AVPlayerLayer(player: videoPlayer!)
//adjust the size and frame
videoPlayerLayer?.frame = CGRect(x: -self.view.frame.size.width*1.5, y:0, width: self.view.frame.size.width*4, height: self.view.frame.size.height)
view.layer.insertSublayer(videoPlayerLayer!, at: 0)
//add and play
videoPlayer?.playImmediately(atRate: 0.8)
}
}
ViewController:
import UIKit
import AVKit
import Firebase
import FirebaseAuth
class ViewController: UIViewController {
var videoPlayer:AVPlayer?
var videoPlayerLayer:AVPlayerLayer?
#IBOutlet weak var signUpButton: UIButton!
#IBOutlet weak var logInButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
if UserDefaults.standard.object(forKey: "user_uid_key") != nil {
print("i see u")
let navController = UINavigationController(rootViewController: HomeViewController())
navController.navigationBar.barStyle = .black
self.present(navController, animated: false, completion: nil)
}
else {
let homeViewController = self.storyboard?.instantiateViewController(identifier: Constants.StoryBoard.homeViewController) as?
ViewController
self.view.window?.rootViewController = homeViewController
self.view.window?.makeKeyAndVisible()
}
// Do any additional setup after loading the view.
setupElements()
}
func showhomepage() {
let homeViewController = self.storyboard?.instantiateViewController(identifier: Constants.StoryBoard.homeViewController) as?
HomeViewController
self.view.window?.rootViewController = homeViewController
self.view.window?.makeKeyAndVisible()
}
override func viewWillAppear(_ animated: Bool) {
//Set up video in background
setUpVideo()
}
func setUpVideo(){
//Get path to resource bundle
let bundlePath = Bundle.main.path(forResource: "Project", ofType: "mp4")
guard bundlePath != nil else{
return
}
//create the url from it
let url = URL(fileURLWithPath: bundlePath!)
//Create The video Player item
let item = AVPlayerItem(url: url)
//create the player
videoPlayer = AVPlayer(playerItem: item)
//create the layer
videoPlayerLayer = AVPlayerLayer(player: videoPlayer!)
//adjust the size and frame
videoPlayerLayer?.frame = CGRect(x: -self.view.frame.size.width*1.5, y:0, width: self.view.frame.size.width*4, height: self.view.frame.size.height)
view.layer.insertSublayer(videoPlayerLayer!, at: 0)
//add and play
videoPlayer?.playImmediately(atRate: 1)
}
func setupElements(){
Utilities.styleFilledButton(signUpButton)
Utilities.styleHollowButton(logInButton)
}
}
Looks like you're using Firebase. Do not store any login information in the User defaults. What you should do is create a blank view controller that will check if the user is signed in. If the user is signed it, it will present your HomeViewController; if the user is not signed in, it will present the login screen. You can also choose to perform these checks in your AppDelegate/SceneDelegate if you want to avoid the extra view controller.
The empty ViewController should be the initial/root ViewController.
You cannot present view controllers from inside viewDidLoad, use viewDidAppear.
Here is a basic example for the view controller way:
// in the new empty view controller, import FirebaseAuth
var handle: AuthStateDidChangeListenerHandle!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(false)
handle = Auth.auth().addStateDidChangeListener { auth, user in
if user != nil {
// Go to Home Screen/ switch root
} else {
// Go to sign in screen/ switch root
}
}
}

Focus textfields in order position with Keyboard. Swift 4.2, Xcode 10

Let's assume there 4 textfields and 1 picker and 1 button in the current view controller. I have added two extra button on keyboard like below:
(Button images are not so important) What am i trying is:
Let's assume I click on second textfield. And then app shows me keyboard with these buttons. I would like to switch textfields with these buttons. First button focus on previous(first textfield) Second button focus on next(third textfield). It should goes on like this in a loop.
Loop means: Let's assume user clicks 4th textfield. And then let's assume user next button on keyboard. So app should must focus on first textfield.
Here is the source code:
import UIKit
class TestViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField1Outlet: UITextField!
#IBOutlet weak var textField2Outlet: UITextField!
#IBOutlet weak var textField3Outlet: UITextField!
#IBOutlet weak var textField4Outlet: UITextField!
#IBOutlet var contentViewOutlet: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.textField1Outlet.delegate = self
self.textField2Outlet.delegate = self
self.textField3Outlet.delegate = self
self.textField4Outlet.delegate = self
let toolBar = UIToolbar()
toolBar.sizeToFit()
let lastItemButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.rewind, target: self, action: #selector(self.switchToTheLastTextfield))
let nextItemButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.fastForward, target: self, action: #selector(self.switchToTheNextTextfield))
toolBar.setItems([lastItemButton, nextItemButton], animated: false)
textField1Outlet.inputAccessoryView = UIView()
textField2Outlet.inputAccessoryView = UIView()
textField3Outlet.inputAccessoryView = UIView()
textField4Outlet.inputAccessoryView = UIView()
textField1Outlet.inputAccessoryView = toolBar
textField2Outlet.inputAccessoryView = toolBar
textField3Outlet.inputAccessoryView = toolBar
textField4Outlet.inputAccessoryView = toolBar
}
#objc func switchToTheLastTextfield(_ textField: UITextField)
{
**// help for this scope please**
}
#objc func switchToTheNextTextfield(_ textField: UITextField)
{
**// help for this scope please**
}
}
I need to write these two methods: switchToTheLastTextfield and switchToTheNextTextfield. Maybe one method is enough to manage a focusing next and previos textfields.
I tried to implement these features with this method below, but it doesn't work fine. I tried this method:
#objc func switchToTheNextTextfield()
{
guard let contentView = self.contentViewOutlet else
{
return
}
for view in contentView.subviews
{
if let tField = view as? UITextField
{
if (tField == textField1Outlet)
{
textField2Outlet.becomeFirstResponder()
}
else if (tField == textField2Outlet)
{
textField3Outlet.becomeFirstResponder()
}
else if (tField == textField3Outlet)
{
textField4Outlet.becomeFirstResponder()
}
else if (tField == textField4Outlet)
{
textField1Outlet.becomeFirstResponder()
}
}
}
}
(Swift 4.2, Xcode 10)
By the way this is a detail: If next item is not a textfield App shouldn't crash. This scenerio should be managed also.
Here, the main idea is to have an array that contains all of your textfields to make assigning first responders cleaner. Once that's set up, it's a matter of setting up your helper functions switchToTheNextTextfield, and switchToTheLastTextfield. In these functions, just iterate through the textfields and see which one is the current firstresponder, then make the next/previous textField in the array become first responder. Check out the sample code and comments.
#IBOutlet weak var textField1Outlet: UITextField!
#IBOutlet weak var textField2Outlet: UITextField!
#IBOutlet weak var textField3Outlet: UITextField!
#IBOutlet weak var textField4Outlet: UITextField!
// have a variable which is an array of the textFields to make it easy to cycle around without a long if condition
lazy var textFields: [UITextField] = [textField1Outlet, textField2Outlet, textField3Outlet, textField4Outlet]
#IBOutlet var contentViewOutlet: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// 1. These can be replaced with below
/*
self.textField1Outlet.delegate = self
self.textField2Outlet.delegate = self
self.textField3Outlet.delegate = self
self.textField4Outlet.delegate = self
*/
// 1. These can replace assigning each of the textFields delegate
textFields.forEach { textField in
textField.delegate = self
}
let toolBar = UIToolbar()
toolBar.sizeToFit()
let lastItemButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.rewind, target: self, action: #selector(self.switchToTheLastTextfield))
let nextItemButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.fastForward, target: self, action: #selector(self.switchToTheNextTextfield))
toolBar.setItems([lastItemButton, nextItemButton], animated: false)
// Don't need these since you'll immediate assign toolBar to the textFields
/*
textField1Outlet.inputAccessoryView = UIView()
textField2Outlet.inputAccessoryView = UIView()
textField3Outlet.inputAccessoryView = UIView()
textField4Outlet.inputAccessoryView = UIView()
*/
// These can also be replaced with the code below
/*
textField1Outlet.inputAccessoryView = toolBar
textField2Outlet.inputAccessoryView = toolBar
textField3Outlet.inputAccessoryView = toolBar
textField4Outlet.inputAccessoryView = toolBar
*/
textFields.forEach { textField in
textField.inputAccessoryView = toolBar
}
}
#objc func switchToTheLastTextfield(_ textField: UITextField)
{
guard let contentView = self.contentViewOutlet else
{
return
}
for (index, textField) in textFields.enumerated() {
if textField.isFirstResponder {
var previousIndex = index == 0 ? textFields.count - 1 : index - 1 // ternary to determine the previous index
textFields[previousIndex].becomeFirstResponder()
return
}
}
}
#objc func switchToTheNextTextfield()
{
guard let contentView = self.contentViewOutlet else
{
return
}
for (index, textField) in textFields.enumerated() {
if textField.isFirstResponder {
var nextIndex = index == textFields.count - 1 ? 0 : index + 1 // ternary to determine the next index
textFields[nextIndex].becomeFirstResponder()
return
}
}
}
Try this solution:
Declare a UITextfield variable and assign the current active textfield in textFieldDidBeginEditing method.
Use your button action method to check the active textfield and use your logic of becomeFirstResponder in it.
var currentTextField = UITextField()
func textFieldDidBeginEditing(_ textField: UITextField) {
self.currentTextField = textField
}
#objc func switchToTheNextTextfield()
{
switch self.currentTextField {
case textField1Outlet:
textField2Outlet.becomeFirstResponder()
case textField2Outlet:
textField3Outlet.becomeFirstResponder()
case textField3Outlet:
textField4Outlet.becomeFirstResponder()
case textField4Outlet:
textField1Outlet.becomeFirstResponder()
default:
print("Out of scope")
}
}
As a general idea I would not cycle through your outlets “manually”: add all relevant (currently 4) inputs to a list and cycle through it with an extra variable that indicates the selected one.

Why are my IBOutlet-connected vars not instantiated when I create an instance?

I set some global variables equal to parameters of a class after it's instantiation. The class is defined as so:
import Foundation
import UIKit
class LogIn : UIViewController{
var test:Int = 1
#IBOutlet var login_icon: UIImageView!
#IBOutlet var username_field: UITextField!
#IBOutlet var password_field: UITextField!
#IBOutlet var login_button: UIButton!
#IBOutlet var createaccount_button: UIButton!
}
The global variables are instantiated as so (within a viewDidAppear method):
func showLoginView(){
self.startup_animation.image = self.seq_6
let login_view = LogIn()
self.login_icon = login_view.login_icon.image //Doesnt work
self.username_field = login_view.username_field //Doesnt work
self.password_field = login_view.password_field //Doesnt work
self.login_button = login_view.login_button //Doesnt work
self.createaccount_button = login_view.createaccount_button //Doesnt work
self.int_testval = login_view.test //Works
}
All IBOutlets-connected variables after instantiation are nil values, but var test is instantiated fine. Why is this?
Thanks!
Additional Context
The showLoginView function is called with the viewDidAppear function. The whole class looks like so:
class StartupViewController: UIViewController {
var images: [UIImage]!
var animatedImage: UIImage!
#IBOutlet var startup_animation: UIImageView!
#IBOutlet var startup_text: UIImageView!
var login_icon: UIImage!
var username_field: UITextField!
var password_field: UITextField!
var login_button: UIButton!
var createaccount_button: UIButton!
var seq_1 : UIImage!
var seq_2 : UIImage!
var seq_3 : UIImage!
var seq_4 : UIImage!
var seq_5 : UIImage!
var seq_6 : UIImage!
var seq_7 : UIImage!
var seq_8 : UIImage!
var rec_1 : UIImage!
var rec_2 : UIImage!
var rec_3 : UIImage!
var rec_4 : UIImage!
var rec_5 : UIImage!
var rec_6 : UIImage!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
seq_1 = UIImage(named: "Eceipt_seq1")
seq_2 = UIImage(named: "Eceipt_seq2")
seq_3 = UIImage(named: "Eceipt_seq3")
seq_4 = UIImage(named: "Eceipt_seq4")
seq_5 = UIImage(named: "Eceipt_seq5")
seq_6 = UIImage(named: "Eceipt_seq6")
seq_7 = UIImage(named: "Eceipt_seq7")
seq_8 = UIImage(named: "Eceipt_seq8")
images = [seq_1 ,seq_2, seq_3, seq_4, seq_5, seq_6, seq_7, seq_8]
animatedImage = UIImage.animatedImage(with: images, duration: 0.8)
startup_animation.image = animatedImage
let transition_text = UIImage(named: "Eceipt_logo")
UIView.transition(with: self.startup_text, duration: 3.0, options: .transitionCrossDissolve,
animations: {
self.startup_text.image = transition_text
},
completion: { (finished: Bool) -> () in
self.showLoginView()
})
}
func showLoginView(){
self.startup_animation.image = self.seq_6
let login_view = LogIn()
self.login_icon = login_view.login_icon.image
self.username_field = login_view.username_field
self.password_field = login_view.password_field
self.login_button = login_view.login_button
self.createaccount_button = login_view.createaccount_button
}
I think you are misunderstanding the use of #IBOutlet.
#IBOutlet just means that it is an outlet of a view or some other object in the xib or storyboard file.
Your views are nil not because they are outlets, but because they are implicitly unwrapped optionals, which is nil if you don't give it a value.
test is not nil, because 1) it is not an optional type and 2) it has a value of 1.
I suggest you put the #IBOutlets in a UIViewController or UIView subclass and connect the appropriate views from the storyboard/xib file to it. This way, they will be initialised by the time the viewDidLoad method or awakeFromNib method is called.
EDIT:
It seems like that you already control+dragged the views to the LogIn class. In that case, check if the circles to the left of the outlets are filled. If they are, that means they are properly connected. You just need to wait until awakeFromNib gets called. If they are not, try connecting them again. Alternatively, just connect all your outlets directly to the view controller, which saves you lots of trouble.
EDIT 2:
Now that I understand what you are trying to do, here is what you should do. You just seems to want to present the LogIn VC. So instead of assigning every view to self, properly present it with the present method.
let vc = UIStoryboard.main!.instantiateViewController(withIdentifier: "Login")
present(vc, animated: true, completion: nil)
Remember to give your login VC in the storyboard a Storyboard ID of "Login".
I think the problem is that you're instantiating the LogIn view controller by doing let login_view = LogIn(). When you instantiate a view controller with its empty initializer, every IBOutlet will be nil because the view controller is not "taken" from a storyboard (or a xib). If you want to use iboutles you must instantiate the view controller from a storyboard or from a xib; in your case (with the storyboard), you should instantiate it by doing:
let storyboard = UIStoryboard(name: "Main", bundle: nil) // name is the filename of the storyboard that contains the vc
let vc = storyboard.instantiateViewController(withIdentifier: "LogIn") as! LogIn // you can cast to have the exact view controller type
present(vc, animated: true, completion: nil)
Make sure that the view controller in your storyboard has the identifier "LogIn".

Swift OSX - Delegate protocol function returns nil, crashes when unwrapping textfield value

I'm working on an OSX app with Swift which makes use of an NSSplitView which holds two view controllers: "TableViewController" and "EntryViewController". I'm using delegates in order to transmit a custom NSObject ("Entry") on click from TableViewController up to the SplitViewController, then back down to the EntryViewController.
My problem is this: When the Entry object is received in the EntryViewController, any attempt to assign its properties to a text field value result in an unexpectedly found nil type error, never mind that the IBOutlets are properly linked, and that it can both print the Entry.property and the textfield string value (provided it is in a different, unrelated function).
I have tried many arrangements to solve this problem, which is why the current configuration might be a bit over-complicated. A delegate relation straight from Table VC to Entry VC caused the same issues.
Is there some way that the IBOutlets are not connecting, even though the view has loaded before the delegate is called? I've read many many articles on delegation—mostly for iOS—and yet can't seem to find the root of my problems. I'll be the first to admit that my grasp of Swift is a little bit piecemeal, so I am open to the possibility that what I am trying to do is simply bad/hacky coding and that I should try something completely different.
Thanks for your help!
TableViewController:
protocol SplitViewSelectionDelegate: class {
func sendSelection(_ entrySelection: NSObject)
}
class TableViewController: NSViewController {
#IBOutlet weak var searchField: NSSearchField!
#IBOutlet var tableArrayController: NSArrayController!
#IBOutlet weak var tableView: NSTableView!
var sendDelegate: SplitViewSelectionDelegate?
dynamic var dataArray = [Entry]()
// load array from .plist array of dictionaries
func getItems(){
let home = FileManager.default.homeDirectoryForCurrentUser
let path = "Documents/resources.plist"
let urlUse = home.appendingPathComponent(path)
let referenceArray = NSArray(contentsOf: urlUse)
dataArray = [Entry]()
for item in referenceArray! {
let headwordValue = (item as AnyObject).value(forKey: "headword") as! String
let defValue = (item as AnyObject).value(forKey: "definition") as! String
let notesValue = (item as AnyObject).value(forKey: "notes") as! String
dataArray.append(Entry(headword: headwordValue, definition: defValue, notes: notesValue))
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.sendDelegate = SplitViewController()
getItems()
print("TVC loaded")
// Do any additional setup after loading the view.
}
// send selection forward to entryviewcontroller
#IBAction func tableViewSelection(_ sender: Any) {
let index = tableArrayController.selectionIndex
let array = tableArrayController.arrangedObjects as! Array<Any>
let obj: Entry
let arraySize = array.count
if index <= arraySize {
obj = array[index] as! Entry
print(index)
print(obj)
sendDelegate?.sendSelection(obj)
}
else {
print("index unassigned")
}
}
}
SplitViewController:
protocol EntryViewSelectionDelegate: class {
func sendSecondSelection(_ entrySelection: NSObject)
}
class SplitViewController: NSSplitViewController, SplitViewSelectionDelegate {
var delegate: EntryViewSelectionDelegate?
#IBOutlet weak var mySplitView: NSSplitView!
var leftPane: NSViewController?
var contentView: NSViewController?
var entrySelectionObject: NSObject!
override func viewDidLoad() {
super.viewDidLoad()
// assign tableview and entryview as child view controllers
let story = self.storyboard
leftPane = story?.instantiateController(withIdentifier: "TableViewController") as! TableViewController?
contentView = story?.instantiateController(withIdentifier: "EntryViewController") as! EntryViewController?
self.addChildViewController(leftPane!)
self.addChildViewController(contentView!)
print("SVC loaded")
}
func sendSelection(_ entrySelection: NSObject) {
self.delegate = EntryViewController() //if this goes in viewDidLoad, then delegate is never called/assigned
entrySelectionObject = entrySelection
print("SVC:", entrySelectionObject!)
let obj = entrySelectionObject!
delegate?.sendSecondSelection(obj)
}
}
And Finally, EntryViewController:
class EntryViewController: NSViewController, EntryViewSelectionDelegate {
#IBOutlet weak var definitionField: NSTextField!
#IBOutlet weak var notesField: NSTextField!
#IBOutlet weak var entryField: NSTextField!
var entryObject: Entry!
override func viewDidLoad() {
super.viewDidLoad()
print("EVC loaded")
}
func sendSecondSelection(_ entrySelection: NSObject) {
self.entryObject = entrySelection as! Entry
print("EVC:", entryObject)
print(entryObject.headword)
// The Error gets thrown here:
entryField.stringValue = entryObject.headword
}
}
You don't need a delegate / protocol since there is a reference to EntryViewController (contentView) – by the way the instance created with EntryViewController() is not the instantiated instance in viewDidLoad.
Just use the contentView reference:
func sendSelection(_ entrySelection: NSObject) {
contentView?.sendSecondSelection(entrySelection)
}

PopUpPicker - does not conform to protocol

I am very new to Swift and programming in general.
I am trying to add a Pop Up Picker on a textfield and when the user selects the item from the picker, they can press OK with that item displayed in the textfield and the PopUp disappear.
I have successfully implemented this with a Pop Up Date Picker as I have used this from GutHub successfully. I thought it would be easy to mimic this code for my Pop Up Picker which has proven to be more difficult than expected.
I have a sepeate XIB file which holds the View with the Picker and OK Button. I then have 2 swift files one for the PopViewController and the other for the PopPicker.
Not even sure if this code is correct but the error I am getting is that my Picker does not conform to protocol. Code is below for both files.
PopEngineViewController
import UIKit
protocol EnginePickerViewControllerDelegate : class {
func enginePickerVCDismissed(string: UITextField?)
}
class PopEngineViewController: UIViewController {
#IBOutlet weak var container: UIView!
#IBOutlet weak var enginePicker: UIPickerView!
weak var delegate : EnginePickerViewControllerDelegate?
override convenience init() {
self.init(nibName: "PopEnginePicker", bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidDisappear(animated: Bool) {
self.delegate?.enginePickerVCDismissed(nil)
}
}
and PopEnginePicker
import UIKit
public class PopEnginePicker : NSObject, UIPopoverPresentationControllerDelegate, EnginePickerViewControllerDelegate {
public typealias PopEnginePickerCallback = (forTextField : UITextField)->()
var enginePickerVC : PopEngineViewController
var popover : UIPopoverPresentationController?
var textField : UITextField!
var dataChanged : PopEnginePickerCallback?
var presented = false
var offset : CGFloat = 8.0
public init(forTextField: UITextField) {
enginePickerVC = PopEngineViewController()
self.textField = forTextField
super.init()
}
public func pick(inViewController : UIViewController, dataChanged : PopEnginePickerCallback) {
if presented {
return // we are busy
}
enginePickerVC.delegate = self
enginePickerVC.modalPresentationStyle = UIModalPresentationStyle.Popover
enginePickerVC.preferredContentSize = CGSizeMake(500,208)
popover = enginePickerVC.popoverPresentationController
if let _popover = popover {
_popover.sourceView = textField
_popover.sourceRect = CGRectMake(self.offset,textField.bounds.size.height,0,0)
_popover.delegate = self
self.dataChanged = dataChanged
inViewController.presentViewController(enginePickerVC, animated: true, completion: nil)
presented = true
}
}
func adaptivePresentationStyleForPresentationController(PC: UIPresentationController!) -> UIModalPresentationStyle {
return .None
}
}
Not even sure if I am going down the complete wrong path however I want it to look like the below as I have done with the date picker as it shows in the link below:
http://coding.tabasoft.it/ios/a-simple-ios8-popdatepicker/