138

I have a protocol

LoginStrategy

public protocol LoginStrategy {
    func login(_ viewController: UIViewController)
    func getUserInfo(withCompletionHandler completionHandler: @escaping (_ userInfo: [String: Any]?) -> ())
    func createLoginButton(_ frame: CGRect, withCompletionHandler completionHandler: @escaping (_ loginButton: UIView) -> ())
    func getUserId() -> String
}

and two classes:

LoginProvider

public class LoginProvider {
    
    public let strategy: LoginStrategy
    
    public func login(_ viewController: UIViewController) {
        return self.strategy.login(viewController)
    }
    
    public func getUserInfo(withCompletionHandler completionHandler: @escaping (_ userInfo: [String: Any]?) -> ()) {
        return self.strategy.getUserInfo(withCompletionHandler: completionHandler)
    }
    
    public func createLoginButton(_ frame: CGRect, withCompletionHandler completionHandler: @escaping (_ loginButton: UIView) -> ()) {
        return self.strategy.createLoginButton(frame, withCompletionHandler: completionHandler)
    }
    
    public func getUserId() -> String {
        return self.strategy.getUserId()
    }
    
    public init(strategy: LoginStrategy) {
        self.strategy = strategy
    }
    
}

FacebookLoginStrategy

import Foundation
import FacebookCore
import FacebookLogin

public class FacebookLoginStrategy: LoginStrategy {
    
    public var grantedPermissions: Set<Permission>? = nil

    public var declinedPermissions: Set<Permission>? = nil
    
    public var userId: String = ""
    
    public func login(_ viewController: UIViewController) {
        let loginManager = LoginManager()
        let permissions: [ReadPermission] = [.publicProfile, .userFriends, .email]
        loginManager.logIn(permissions, viewController: viewController) { loginResult in
            switch loginResult {
            case .failed(let error):
                print(error)
            case .cancelled:
                print("User cancelled login.")
            case .success(let grantedPermissions, let declinedPermissions, let accessToken):
                self.userId = accessToken.userId ?? ""
                self.grantedPermissions = grantedPermissions
                self.declinedPermissions = declinedPermissions
                print("Logged in!")
            }
        }
    }
    
    public func getUserId() -> String {
        return userId
    }
    
    public func getUserInfo(withCompletionHandler completionHandler: @escaping (_ userInfo: [String: Any]?) -> ()) {
        let request = GraphRequest(graphPath: "me", parameters: ["fields":"email, name"], accessToken: AccessToken.current, httpMethod: .GET, apiVersion: FacebookCore.GraphAPIVersion.defaultVersion)
        request.start { (response, result) in
            switch result {
            case .success(let value):
                print(value.dictionaryValue)
                completionHandler(value.dictionaryValue)
            case .failed(let error):
                print(error)
            }
        }
    }

    public func createLoginButton(_ frame: CGRect, withCompletionHandler completionHandler: @escaping (_ loginButton: UIView) -> ()) {
        let permissions: [ReadPermission] = [.publicProfile, .userFriends, .email]
        let loginButton = LoginButton(readPermissions: permissions)
        loginButton.frame = frame
        completionHandler(loginButton)
    }
}

In my ViewController:

When I use:

let facebookLoginProvider = LoginProvider(strategy: FacebookLoginStrategy())

It says:

'FacebookLoginStrategy' is inaccessable due to 'internal' protection level

Twitter khuong291
  • 11,328
  • 15
  • 80
  • 116

4 Answers4

363

Just add to your FacebookLoginStrategy:

public init() {}

As long as you do not implement init() explicitly, it is marked as internal by default. You need to overwrite that permission level to be able to instantiate from outside your framework.

jboi
  • 11,324
  • 4
  • 36
  • 43
  • 15
    Great answer! And watch out for Swift4, it's actually an error if you don't implement it. – Paul Razvan Berg Aug 10 '17 at 20:38
  • 2
    Just into the class. – jboi Feb 10 '18 at 13:26
  • 54
    this is so dumb that Swift requires this. If I declare the whole class type public, the free init() should come as public as well – LightningStryk May 15 '19 at 22:50
  • 4
    This should a bug of `Swift`. It should not an error of compiler, since we had marked `public` for `class/struct`. – lee Oct 31 '19 at 11:47
  • Just learned, that it also works for `structs`. Got the error-message, was confused, googled and found my own answer. It's getting late on a Friday... – jboi Nov 01 '19 at 19:00
  • 1
    I've added public before my init and I am still getting that same message. Is there another protection? – AndreG Nov 12 '19 at 22:04
  • Hi @AndreG , can you share some code with us? Maybe just ask your question. – jboi Nov 13 '19 at 06:41
  • 1
    @lee It's not dumb, nor is it a bug. Sometimes you don't want the initializers for a type to be public. It's easy to forget about the existence of synthesized initializers because they don't actually appear in your code. Therefore, if these initializers were public, it'd be easy to accidentally expose them outside of the module. – Peter Schorn Jan 24 '22 at 14:49
  • @LightningStryk This intended and I hope it stays this way, sometime you don't want all your init being public. On the other hand, the documentation, should be more clear about this. – iPadawan Aug 21 '23 at 11:47
21

If you are running in to this in code within an XCTestCase, make sure that you have added @testable import My-Awesome-App to the top of your test file.

Alex Zavatone
  • 4,106
  • 36
  • 54
7

Add init method as Public access

public init() {}
Yogesh Rathore
  • 147
  • 1
  • 7
2

In case anyone faces this issue when implementing Swift packages for building reusable SwiftUI views, there are a couple of things to keep in mind.

  1. If there are component props that are being set from the parent component, don't forget to add them to the constructor
  2. If the property is wrapped with @State, then assign the state to the underlying object as shown below.
public struct Layout: View {
    @State var layout: NavType = .TOP
    var navItems: [NavItemObject]
    public init(layout: NavType, navItems: [NavItemObject]) {
        _layout = State(initialValue: layout);
        self.navItems = navItems;
    }
}
Aravind Samala
  • 139
  • 1
  • 5