Required Init throwing fatal error when implementing Stripe - ios

I am working on adding Stripe into my project. There's more code than this, but it was all working before I began adding in Stripe and the init. This is the init that Stripe says to use in their docs
Here's my beginning code and init code:
class BusinessOwnerVC: UIViewController, MyProtocol {
let paymentContext: STPPaymentContext
init() {
let customerContext = STPCustomerContext(keyProvider: SwiftAPI())
self.paymentContext = STPPaymentContext(customerContext: customerContext)
super.init(nibName: nil, bundle: nil)
self.paymentContext.delegate = self
self.paymentContext.hostViewController = self
self.paymentContext.paymentAmount = 5000 // This is in cents, i.e. $50 USD
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
.....
I am using Storyboards, as I've heard that matters. When I run the code, the fatalError gets thrown and crashes the app. The Stripe example project has this exact code in there, and it works.
Why is my app crashing? What is this required init even doing? I think I understand why I need it, but if you could elaborate beyond it's required for subclasses, that would be helpful.

The solution to my issue was removing the init and only user the required init, like so:
required init?(coder aDecoder: NSCoder) {
//fatalError("init(coder:) has not been implemented")
let customerContext = STPCustomerContext(keyProvider: SwiftAPI())
self.paymentContext = STPPaymentContext(customerContext: customerContext)
super.init(coder: aDecoder)
self.paymentContext.delegate = self
self.paymentContext.hostViewController = self
self.paymentContext.paymentAmount = 5000 // This is in cents, i.e. $50 USD
}
I left the commented fatelError portion, but as you can see it's not necessary. It's like the others said, the required init gets used by storyboards and you must have it when you're setting up data in your storyboard class, like Stripe requires.
Just have the super.init in there and you should be all good to go.

Related

Is it possible to initialize variable depending on another in class inheriting from UIViewController in Swift?

I'm trying to achieve a seemingly easy thing: initialize constant variable using another one from the same class which is inheriting from UIViewController. I had 2 ideas that worked, but have their problems and don't seem to be the best solution.
Idea 1 - problem: isn't constant
class MyViewController: UIViewController {
let db = Firestore.firestore()
let uid: String = UserDefaults.standard.string(forKey: "uid")!
lazy var userDocRef = db.collection("users").document(uid)
}
Idea 2 - problem: isn't constant and is optional
class MyViewController: UIViewController {
let db = Firestore.firestore()
let uid: String = UserDefaults.standard.string(forKey: "uid")!
var userDocRef: DocumentReference?
override func viewDidLoad() {
super.viewDidLoad()
userDocRef = db.collection("users").document(uid)
}
}
I think it should be possible to achieve that by overriding init(). I've tried couple of implementations I found Googling, but everyone I've tried gave me some kind of error. Few examples:
From this answer.
convenience init() {
self.init(nibName:nil, bundle:nil) // error: Argument passed to call that takes no arguments
userDocRef = db.collection(K.Firestore.usersCollection).document(uid)
}
From this article.
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)! // Property 'self.userDocRef' not initialized at super.init call
}
init() {
super.init(nibName: nil, bundle: nil) // Property 'self.userDocRef' not initialized at super.init call
userDocRef = db.collection(K.Firestore.usersCollection).document(uid)
}
I'm guessing I'm either missing something or those are outdated? I'm surprised such a simple task as overriding initializer is such a bother. What is the proper way to do it?
Your second try is quite close. You've really just missed this rule that you have to follow:
all properties declared in your class must be initialised before calling a super.init.
In your second try, userDocRef is not initialised in init(coder:) and initialised after a super.init call in init().
You should write it like this:
init() {
userDocRef = db.collection(K.Firestore.usersCollection).document(uid)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
userDocRef = db.collection(K.Firestore.usersCollection).document(uid)
super.init(coder: coder)
}
I don't think you can get rid of the duplicate code here... The best you can do is to create a static helper method that returns db.collection(K.Firestore.usersCollection).document(uid), which means making db static as well, which might be worse now that I think about it.
Note that anything from the storyboards will be created using init(coder:), so it is important that you do your initialiser there properly as well if you are using storyboards.

Swift error from required convenience for viewcontroller

I'm playing around with having a factory that sets up a login in a view controller to satisfy myself what is going on (this is old code I'm updating) I'm trying to make this as simple an example as possible.
It crashes!
View Controller
init(loginServerClass: String){
super.init(nibName: nil, bundle: nil)
}
//called to initialize the login server class
public init?(coder aDecoder: NSCoder, loginServerClass: String){
super.init(coder: aDecoder)
}
//called from storyboard
required convenience init?(coder aDecoder: NSCoder) {
self.init(coder: aDecoder)
}
using a LoginFactory
public protocol LoginFactoryProtocol{
static func createLogin () -> String
}
class LoginFactory : LoginFactoryProtocol {
static public func createLogin () -> String {
return "testlogintype"
}
}
So
1). It crashes with the minimal example above (bad access)
2). It crashes when I add my prefered convenience int as below:
required convenience init?(coder aDecoder: NSCoder) {
self.init(coder: aDecoder, loginServerClass: LoginFactory.createLogin() )
}
(This coder requires that replaced objects be returned from initWithCoder:)
How can I compile this minimum code?
Github link https://github.com/stevencurtis/initissue
If downvoters could explain why, that would be appreciated as I can't improve without knowing what needs to be added. I already added the error messages, a Github link with the problem. I've listed what I've tried, and I'm going from existing previously working code to a small example (therefore I HAVE tried things, the documentation is https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-XID_324 and similar questions on stack overflow have not helped (they seem to have the same issues i.e. (NSGenericException: This coder requires that replaced objects be returned from initWithCoder))). By definition this is a small example of the problem. I believe this is how a question should be structured (from https://stackoverflow.com/help/how-to-ask), so please do tell me: what else do I need to do to write a good question, please?
One solution (and I don't know if it is good practice) seems to be to use a single initializer that calls super.init
public required init?(coder aDecoder: NSCoder) {
loginServerClass = LoginFactory.createLogin()
super.init(coder: aDecoder)
}
and then have a single intializer for testing and mocking (or to create the viewcontroller otherwise from code)
init(loginServerClass: String){
self.loginServerClass = loginServerClass
super.init(nibName: nil, bundle: nil)
}

'self' used before super.init call and property not initialized at super.init call

I'm trying to init an array of Int in my custom UIView:
var graphPoints:[Int]
required init?(coder aDecoder: NSCoder) {
RestApiManager.sharedInstance.requestData() { (json: JSON) in
for item in json[].array! {
let n=item["n"].intValue
print(n)
if(n>=0) {
self.graphPoints.append(n)
}
}
}
super.init(coder: aDecoder)
}
But int this row RestApiManager.sharedInstance.requestData() { i reiceved this error: 'self' used before super.init call
And at this row super.init(coder: aDecoder) the error: property self.graphPoint not initialized at super.init call
There are a few seperate issues here
The first related two you question being that you must call the super.init method before you reference self. Simply move this line to the beginning on the init method.
Unfortunately because you are preforming an asynchronous request this will still cause you problems as mentioned in the comments.
Also, note init?(coder aDecoder: NSCoder) is not intended for this use. Another issue is if you are requesting data from your API you should create a model object. As opposed to directly creating a UIView and then when you wish to display it to a user create a UIView from that model.
class GraphPointsModel {
var graphPoints:[Int]
init(graphPoints: [Int]) {
self.graphPoints = graphPoints
}
class func retreiveGraphPoints(handler: (GraphPointsModel -> ())) {
RestApiManager.sharedInstance.requestData() { (json: JSON) in
//Error checking...
//Create instance of GraphPointsModel
let instance = GraphPointsModel(...)
//Call handler to do with as you wish with result
handler(instance)
}
}
}
Swift requires all parameters to be initialized before calling super.init(). There are several ways to make this happen.
Declare graphPoints with an empty initializer like this:
var graphPoints:[Int] = [] or var graphPoints = [Int]()
You can also change the graphPoints to an Optional, like this:
var graphPoints:[Int]?
You can also leave the declaration alone, and just initialize it to an empty array before calling super.init()
You will also need to move your RestAPI call below your super.init call.
Hope this helps.
Maybe you must to do this:
super.init(coder: aDecoder)
put that before your requestData call.

NSCoding required initializer in inherited classes in Swift

I have class Foo which conforms to NSObject and NSCoding which I want to be able to persist with NSKeyedArchiver I want to create class Bar, a subclass of Foo that will also conform to NSObject and NSCoding. I am having a problem understanding how to create the required convenience init?(coder aDecoder: NSCoder) in the subclass.
so class Foo...
class Foo: NSObject, NSCoding {
let identifier:String
init(identifier:String) {
self.identifier = identifier
}
override var description:String {
return "Foo: \(identifier)"
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(identifier, forKey: "identifier")
}
required convenience init?(coder aDecoder: NSCoder) {
guard let identifier = aDecoder.decodeObjectForKey("identifier") as? String
else {
return nil
}
self.init(identifier:identifier)
}
}
Then class Bar ...
class Bar:Foo {
let tag:String
init(identifier:String, tag:String) {
self.tag = tag
super.init(identifier: identifier)
}
override var description:String {
return "Bar: \(identifier) is \(tag)"
}
}
I can get this to compile by adding the following methods on to make this NSCoding compliant
override func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(tag, forKey: "tag")
super.encodeWithCoder(aCoder)
}
this makes sense because I call super.encodeWithCoder(...) reusing the super makes this DRY. The problem I am having is creating the required convenience init?(...) the only way I can seem to get it to compile is by doing this...
required convenience init?(coder aDecoder:NSCoder) {
guard let identifier = aDecoder.decodeObjectForKey("identifier") as? String,
let tag = aDecoder.decodeObjectForKey("tag") as? String
else {
return nil
}
self.init(identifier:identifier, tag:tag)
}
I basically have copied the superclass required initializer and then added the additional decode method for the subclass property. This approach does not seem correct...
Is there a better way to implement this??
Right after you decode and assign all the subclass properties in the required init method, call:
super.init(coder: aDecoder)
Have thought about this for a while and believe that this is the correct way to implement this.
The reason is the way Swift enforces object initialization. Convenience initializers can only call the required initializers on self. Only the required initializer can call the init on super.
Therefore the only way to initialize the subclass object is to decode all of the required initialization parameters before you call the subclass required initializer...which then calls the super class initializer
Here is code you can copy to a playground https://gist.github.com/vorlando/dc29ba98c93eaadbc7b1
I have try your code in playground, it just auto to add the code when I tick the red circle of the Error.
The coding is like your function required convenience init.
sample code:
required convenience init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}

Twitter Fabric "Show Timelines" Swift example failing with "Cannot override 'init' which has been marked unavailable"

I'm implementing the Twitter Fabric iOS Show Timelines example exactly as described in the Twitter documentation:
https://dev.twitter.com/twitter-kit/ios/show-timelines
However, I'm getting a build error:
Cannot override 'init' which has been marked unavailable
...on the "required init" line below.
I appreciate any help you can provide this newbie. Thanks in advance.
import UIKit
import TwitterKit
class ViewController: TWTRTimelineViewController {
convenience init() {
let client = Twitter.sharedInstance().APIClient
let dataSource = TWTRUserTimelineDataSource(screenName: "fabric", APIClient: client)
self.init(dataSource: dataSource)
}
override required init(dataSource: TWTRTimelineDataSource) {
super.init(dataSource: dataSource)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Twitter dev support confirms that this is a bug and they'll ship a fix ASAP. Here is the link to the issue on the Twitter dev forums:
https://twittercommunity.com/t/show-timelines-swift-example-failing-with-cannot-override-init-which-has-been-marked-unavailable/37853
Also add Following initializer
required convenience init(coder aDecoder: NSCoder) {
self.init()
}
Update the method declaration from TWTRTimelineViewController.h of TwitterKit header files as follows:
- (id)initWithCoder:(NSCoder *)aDecoder;// __attribute__((unavailable("Use -initWithDataSource: instead")));
Here I have commented __attribute__((unavailable as we are using this initWithDataSource: in our convenience init.
This is temporary workaround and anyone can update this answer if needed. I will also post update if any.

Resources