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:).

Vogel Vogel
  • 311
  • 1
  • 4
  • 12

2 Answers2

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
    }
}
Nate Cook
  • 92,417
  • 32
  • 217
  • 178
  • 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... – Nicolas Miari Dec 05 '16 at 13:40
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

DZoki019
  • 382
  • 2
  • 13
Knight0fDragon
  • 16,609
  • 2
  • 23
  • 44
  • 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. – arsenius Jul 30 '19 at 02:54
  • @arsenius 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 given – Knight0fDragon Jul 30 '19 at 02:58
  • "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. – arsenius Jul 30 '19 at 03:07
  • 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. – Knight0fDragon Jul 30 '19 at 03:11