4

I have two xib file, one which shows login view and another which shows the steps what to do after the login is successful. I am having hard time to make it work. I have created macos project not ios and using safariservices so that it will work for the safari extension either.

Here is what i have done

import SafariServices


class SafariExtensionViewController: SFSafariExtensionViewController {


    @IBOutlet weak var passwordMessage: NSTextField!
    @IBOutlet weak var emailMessage: NSTextField!
    @IBOutlet weak var message: NSTextField!
    @IBOutlet weak var email: NSTextField!
    @IBOutlet weak var password: NSSecureTextField!
    static let shared = SafariExtensionViewController()
    override func viewDidLoad() {
        self.preferredContentSize = NSSize(width: 300, height: 250)
        message.stringValue = ""
        emailMessage.stringValue = ""
        passwordMessage.stringValue = ""
    }
    override func viewDidAppear() {
        if let storedEmail = UserDefaults.standard.object(forKey: "email") as? String {
            if let stepView = Bundle.mainBundle.loadNibNamed(NSNib.Name(rawValue: "ExtensionStepsViewController"), owner: nil, topLevelObjects: nil)[0] {
                self.view.addSubview(stepView)
            }
        }
    }

    @IBAction func userLogin(_ sender: Any) {
        let providedEmailAddress = email.stringValue
        let providedPassword = password.stringValue
        let isEmailAddressValid = isValidEmailAddress(emailAddressString: providedEmailAddress)
        self.message.stringValue = ""
        emailMessage.stringValue = ""
        passwordMessage.stringValue = ""
        if isEmailAddressValid && providedPassword.count > 0 {
          /* login process is handled here and store the email in local storage /*
          /* TODO for now email is not stored in browser localstorage which has to be fixed */
         let controller = "ExtensionStepsViewController"
         let subview = ExtensionStepsViewController(nibName: NSNib.Name(rawValue: controller), bundle: nil)
         self.view.addSubview(subview.view)
         }   
}
}

This way i get error like Type Bool has no subscript members my file structure looks something like this.

SafariExtensionViewController.xib (main one which is shown initially with login screen)

SafariExtensionViewController.swift

ExtensionStepsViewController.xib(this view should be shown when user is logged in instead of login screen)

ExtensionStepsViewController.swift

I am using xcode 10, swift 4, everything new.

UPDATE

I used the following block both in viewDidAppear(if there is email in localstorage then show extension steps view instead of login screen) and inside login function when the login is success but it does not navigate to that ExtensionStepsView

let controller = "ExtensionStepsViewController"
let subview = ExtensionStepsViewController(nibName: NSNib.Name(rawValue: controller), bundle: nil)
self.view.addSubview(subview.view)

Use case is show login at initial but if user is logged in then show another view but issue is now the view are merged

enter image description here

1 Answers1

1

You got the error "Type Bool has no subscript members" because loadNibNamed(_:owner:topLevelObjects:) method of Bundle returns Bool struct that has no subscript members so you can't write like

true[0]

How to use this method correctly see the link and example from there:

var topLevelObjects : NSArray?
if Bundle.main.loadNibNamed(NSNib.Name(rawValue: "ExtensionStepsViewController"), owner: self, topLevelObjects: &topLevelObjects) {
     let  topLevelObjects!.first(where: { $0 is NSView }) as? NSView
}

Views were merged because you didn't remove previous views from the superview and added view from ExtensionStepsViewController to the same superview.

You can do the following steps to complete your issue:

  1. Make SafariExtensionViewController inherited from SFSafariExtensionViewController that will be container (and parent) for two child view controllers such as LoginViewController and ExtensionStepsViewController and will be used to navigate between ones.
  2. Make separately LoginViewController and ExtensionStepsViewController (both inherited from simple NSViewController) and its xibs.
  3. Right after user logins transit from LoginViewController to ExtensionStepsViewController

As an example but instead of ParentViewController you have to use your implementation SafariExtensionViewController as I explain above in the first step.

public protocol LoginViewControllerDelegate: class {
    func loginViewControllerDidLoginSuccessful(_ loginVC: LoginViewController)
}

public class LoginViewController: NSViewController {
    weak var delegate: LoginViewControllerDelegate?

    @IBAction func login(_ sender: Any) {
        // login logic
        let isLoginSuccessful = true
        if isLoginSuccessful {
            self.delegate?.loginViewControllerDidLoginSuccessful(self)
        }
    }
}

public class ExtensionStepsViewController: NSViewController {

}

public class ParentViewController: NSViewController, LoginViewControllerDelegate {
    weak var login: LoginViewController! // using of force unwrap is anti-pattern. consider other solutions
    weak var steps: ExtensionStepsViewController!

    public override func viewDidLoad() {
        let login = LoginViewController(nibName: NSNib.Name(rawValue: "LoginViewController"), bundle: nil)
        login.delegate = self
        // change login view frame if needed
        login.view.frame = self.view.frame
        self.view.addSubview(login.view)
        // instead of setting login view frame you can add appropriate layout constraints
        self.addChildViewController(login)
        self.login = login

        let steps = ExtensionStepsViewController(nibName: NSNib.Name(rawValue: "ExtensionStepsViewController"), bundle: nil)
        steps.view.frame = self.view.frame
        self.addChildViewController(steps)
        self.steps = steps
    }

    // MARK: - LoginViewControllerDelegate

    public func loginViewControllerDidLoginSuccessful(_ loginVC: LoginViewController) {
        self.transition(from: self.login, to: self.steps, options: .slideLeft) {
            // completion handler logic
            print("transition is done successfully")
        }
    }
}

Here is a swift playground with this example.

UPD:

You can instantiate NSViewController in several ways:

  1. Use NSStoryboard that allows to load view of NSViewController from .storyboard file:
let storyboard = NSStoryboard(name: NSStoryboard.Name("NameOfStoryboard"), bundle: nil)
let viewController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("NSViewControllerIdentifierInStoryboard"))
  1. Use appropriate initialiser of NSViewController to load view of it from .xib file:
let steps = ExtensionStepsViewController(nibName: NSNib.Name(rawValue: "ExtensionStepsViewController"), bundle: nil)
  1. Use default initialiser but you have to load view directly by overriding loadView() method if name of xib file is different from name of view controller class:
let steps = ExtensionStepsViewController()
// Also you have to override loadView() method of ExtensionStepsViewController.
ezaji
  • 304
  • 2
  • 9
  • Can you once check at this, please? I tried to do as per your explanation and code but could not understand where logindelegate code should reside https://gist.github.com/tushantOffice/46dd0c15434144fb42d23882b07b4f15 – Tushant Khatiwada Sep 06 '18 at 08:56
  • 1
    LoginViewControllerDelegate is a protocol like an interface from another programming languages. Your SafariExtensionViewController (explained in the first step) should conform this protocol and implement all method from it. – ezaji Sep 06 '18 at 09:01
  • 1
    LoginUIController & ExtensionStepsViewController might be inherited from simple NSViewController. – ezaji Sep 06 '18 at 09:02
  • 1
    see [Delegation pattern](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID276) – ezaji Sep 06 '18 at 09:04
  • I updated the gist and i did that way where i get error in the line `login.delegate = self` it says `Cannot assign value of type 'SafariExtensionViewController' to type 'LoginViewControllerDelegate?'` – Tushant Khatiwada Sep 06 '18 at 09:13
  • this [playground](https://drive.google.com/file/d/1v_GReEyu4k_dfRrdM96olTqW5TyhVgWZ/view?usp=sharing) shows that it should be worked – ezaji Sep 06 '18 at 09:20
  • Can you load the minimum project that reproduces that error, please? – ezaji Sep 06 '18 at 09:24
  • Are you sure that this error occurs still? Your project is compiled and ran successfully. – ezaji Sep 06 '18 at 09:45
  • set layout constraints for subviews on .xib files (in LoginViewController for Login button etc.) – ezaji Sep 06 '18 at 09:48
  • I have done the preferredContentSize and also set the button to show up in the center from size inspector – Tushant Khatiwada Sep 06 '18 at 10:07
  • I think you have to set correct frame for views of child view controllers and preferredContentSize for SafariExtensionViewController. See [example](https://drive.google.com/file/d/1asX5IA5eLQv8ZYzvXa5aF2AktvlZhzOA/view?usp=sharing) – ezaji Sep 06 '18 at 10:46
  • Also I recommend to read about [Coordinate System](https://developer.apple.com/library/archive/documentation/General/Devpedia-CocoaApp-MOSX/CoordinateSystem.html) on macOS. The following [question](https://stackoverflow.com/questions/21751105/mac-os-x-convert-between-nsview-coordinates-and-global-screen-coordinates) might be useful too. – ezaji Sep 06 '18 at 11:27