In current FacebookLogin version (0.2.0) for Swift, the LoginButton delegate property is defined as a strong property:
public class LoginButton: UIView {
...
/// Delegate of the login button that can handle the result, logout events.
public var delegate: LoginButtonDelegate?
... }
If you add the login button following Facebook instructions and you set your UIViewController child class as button delegate...
import FacebookLogin
func viewDidLoad() {
let loginButton = LoginButton(readPermissions: [ .PublicProfile ])
loginButton.center = view.center
loginButton.delegate = self
view.addSubview(loginButton)
}
... a reference cycle will be created. The view will contain a strong reference to the button, the button will contain a strong reference to the controller, and the controller will have a strong reference to its view, see this post.
My solution was to use a weak member variable to have a reference to the login button and when the view disappears, the button delegate is set to nil, like this:
import UIKit
import FacebookCore
import FacebookLogin
import RxSwift
class LoginViewController: UIViewController, LoginButtonDelegate {
private weak var facebookLoginButton: LoginButton? = nil
override func viewDidLoad() {
super.viewDidLoad()
// Add the Facebook login button
let loginButton = LoginButton(readPermissions: [ .publicProfile, .email, .userFriends ])
loginButton.center = view.center
// WARNING!: Facebook login button delegate property is defined currently as STRONG.
// Therefore, it must be set to nil before leaving the view to avoid reference cycles
loginButton.delegate = self
view.addSubview(loginButton)
// Store the login button as a weak reference, since it is holded by the main view with a
// strong reference
facebookLoginButton = loginButton
}
override func willMove(toParentViewController parent: UIViewController?) {
super.willMove(toParentViewController:parent)
if parent == nil {
// The back button was pressed, interactive gesture used, or programatically pop view
// was executed
// Do not forget to set delegate in Facebook button to nil to break reference cycle.
facebookLoginButton?.delegate = nil
}
}
// MARK: - Facebook login
/**
Called when the button was used to login and the process finished.
- parameter loginButton: Button that was used to login.
- parameter result: The result of the login.
*/
func loginButtonDidCompleteLogin(_ loginButton: LoginButton, result: LoginResult) {
switch result {
case .failed(let error):
// Action on failed
case .cancelled:
// Action on cancelled
case .success(let grantedPermissions, let declinedPermissions, let accessToken):
// Action on success
}
}
/**
Called when the button was used to logout.
- parameter loginButton: Button that was used to logout.
*/
func loginButtonDidLogOut(_ loginButton: LoginButton) {
// Action on logout
}
}
Do not use function viewWillDissapear() for setting to nil the delegate, because Facebook login page will be shown on top of your app, triggering this function, and you will not get the login result since you will not be the delegate anymore. Note that this solution is working fine for views inside a navigation controller. Another solution should be found for modal windows.
I hope it helps,
Xavi