How to return a custom object in a swift convenience initializer?
Asked Answered
B

2

5

I'm trying to do something like this:

public extension UIImage {
    public convenience init(whatever: Int) {
        UIGraphicsBeginImageContextWithOptions(...)

        //...

        let image = UIGraphicsGetImageFromCurrentContext()
        UIGraphicsEndImageContext()

        return image // <- impossible
    }
}

But this is not possible as "nil" is the only valid return for an initializer... How do i do this?

For example, the Objtive-C method [UIImage imageNamed:] is a class method (that can return whatever it wants in Objective-C) and it was mapped to the swift initializer UIImage(named:).

Berners answered 19/10, 2015 at 17:51 Comment(0)
B
7

What you want is a class factory method, not an initializer. Most factory methods in Foundation/Cocoa are automatically bridged to initializers, but if what you want can't be done via init, you can add a new class method:

public extension UIImage {
    class func imageWithWhatever(whatever: Int) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(...)

        //...

        let image = UIGraphicsGetImageFromCurrentContext()
        UIGraphicsEndImageContext()

        return image
    }
}
Brigette answered 19/10, 2015 at 18:8 Comment(1)
It is a bit sad you can't return a different object in Swift. I have this framework with this complex view controller defined in a storyboard. I would like to be able to override init() or even create a custom initializer to instantiate the view controller programmatically, and have it all setup with its subviews/outlets etc. But the only way is to use a factory method...Xylidine
S
1

This is because you are returning a new object, not self. The point of init is to create the structure of your object, not a new one, so if you want to do it as a convenience init, you need to do it like this:

public extension UIImage {
    public convenience init?(whatever: Int) {
        defer {
            UIGraphicsEndImageContext()
        }
        UIGraphicsBeginImageContextWithOptions(...)

        //...
        guard let currentContext = UIGraphicsGetCurrentContext() else { return nil }
        guard let image = currentContext.makeImage() else { return nil }

        self.init(cgImage:image)
    }
}

perhaps instead of a convenience init, you want to create a class function that is doing what you are asking:

public class func createImage(whatever: Int) -> UIImage? {
    defer {
        UIGraphicsEndImageContext()
    }
    UIGraphicsBeginImageContextWithOptions(...)

    //...
    guard let currentContext = UIGraphicsGetCurrentContext() else { return nil }
    guard let cgImage = currentContext.makeImage() else { return nil }
    let image = UIImage(cgImage: cgImage)

    return image
}

I apologize that this is not 100% to code, but that is basically the gist of it

Snowman answered 19/10, 2015 at 18:16 Comment(4)
Important to note that after calling self.init(...) you can also modify self like self.foo = Foo() before the end of the function. Not terribly useful in the case of UIImage but the question title doesn't specify that it needs to be.Deliberation
@Deliberation that note is not important at all in this context, you can only manipulate a classes properties after it is initialized, that should be a givenSnowman
"You can manipulate properties after initialization" is my point. You can't do let image = UIImage()...return image but you can modify self which covers many use cases of convenience initializers. The OP or others happening on this thread may not have realized that.Deliberation
Modifying self is pointless in this regard, you are just adding to the confusion. Stick to what the question asks, people are looking for concise answers on SO.Snowman

© 2022 - 2024 — McMap. All rights reserved.