How to return a custom object in a swift convenience initializer? - objective-c

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

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
}
}

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

Related

Return type of UIImage in Swift [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
I have a function which returns image if condition is satisfied else it returns nil
- (UIImage *)getProfilePic{
if (doc.userProperties != nil) {
UIImage *image = [UIImage imageNamed:#"placeholder.png"];
return image;
}
else {
return nil;
}
}
I want to convert this is in swift. I have tried this but it shows error while returning nil and also it crashes by showing error as unwrapping nil.
func getProfilePic(){
var image : UIImage?
if doc!.userProperties != nil {
image = UIImage(named: "placeholder.png")!
return image!
}
else {
return nil
}
}
at "return nil" line it shows nil is not compatible with return type ' uiimage'
func getProfilePic(){
var image : UIImage?
if doc!.userProperties != nil {
image = UIImage(named: "placeholder.png")!
return image!
}
else {
return nil
}
}
Right, so there are a few problems here. First, your code doesn't have a return type. If we ignore the body of your method and look just at the signature, the Objective-C equivalent would look like this:
- (void)getProfilePic;
So Swift & Objective-C would be complaining about the same thing here: what you're trying to return and the declared return type of the method do not match.
In case it's helpful since you seem perhaps more familiar with Objective-C than Swift, here's what your Swift method would look like if we translated it back into Objective-C:
- (void)getProfilePic {
UIImage *image;
if (doc.userProperties) {
image = UIImage(named: #"placeholder.png");
return image;
}
else {
return nil;
}
}
And again, this would generate the same or similar compile time warnings or errors, because the return type does not match the method signature. But Objective-C would not crash for unwrapping nil (but Swift will).
What you're actually trying to return is a UIImage?, so we need to update our method signature.
func getProfilePic() -> UIImage? {
if doc?.userProperties != nil {
return UIImage(named: "placeholder.png")
}
return nil
}
Assuming that userProperties holds perhaps a URL to an image you want to download or maybe the image itself, in the future we're going to want a slightly different construct... something more like this:
func getProfilePic() -> UIImage? {
guard let userProperties = doc?.userProperties else {
return nil
}
// extract the image from userProperties and return it
}
Try this code sample :
func getProfilePic() -> UIImage? {
let imageName = "placeholder.png"
var image: UIImage?
if (doc.userProperties != nil) {
image = UIImage(named: imageName)
}
return image
}

How can method in Swift with inout parameter be used in Objective-C?

I want
func foo(inout stop: Bool) -> Void {
// ...
}
use in my Objective-C part. But it is never generated in Module-Swift.h header. If I mark it with #objc, the
Method cannot be marked #objc because the type of the parameter cannot
be represented in Objective-C
error occurs.
You can't use an inout parameter when bridging with Objective-C, but you can do something similar if you use an UnsafeMutablePointer<T> (T would be Bool in your case). It would look something like this:
#objc func foo(stop: UnsafeMutablePointer<Bool>) -> Void {
if stop != nil {
// Use the .pointee property to get or set the actual value stop points to
stop.pointee = true
}
}
Example
TestClass.swift:
public class TestClass: NSObject {
#objc func foo(stop: UnsafeMutablePointer<Bool>) -> Void {
stop.pointee = true
}
}
Objective-C:
TestClass *test = [[TestClass alloc] init];
BOOL stop = false;
[test foo:&stop];
// stop is YES here
Similarly to what happening with generics, inout is not objc-compatible.
One possible workaround is to embed your parameter(s) in a class (which is a reference type, hence passed by pointer and not by value):
#objc class MyFuncParams {
var stop: Bool
init(stop: Bool) {
self.stop = stop
}
}
and define the function to accept an instance of that class:
func changeParam(params: MyFuncParams) {
params.stop = true
}
Not an elegant way to solve the problem, but what's important is that it should work (never tried myself though).

Autosave Expanded Items of NSOutlineView doesn't work

I am trying to use the "Autosave Expanded Items" feature. When I expand a group with its children and restart the application all children are collapsed again and I don't know why they won't stay expanded.
I'm using core data to store my source list items.
This is what I have done/set so far:
Checked "Autosave Expanded Items" in NSOutlineView (Source List)
Set a name for "Autosave"
dataSource and delegate outlets assigned to my controller
This is my implementation for outlineView:persistentObjectForItem and outlineView:itemForPersistentObject.
- (id)outlineView:(NSOutlineView *)anOutlineView itemForPersistentObject:(id)object
{
NSURL *objectURI = [[NSURL alloc] initWithString:(NSString *)object];
NSManagedObjectID *mObjectID = [_persistentStoreCoordinator managedObjectIDForURIRepresentation:objectURI];
NSManagedObject *item = [_managedObjectContext existingObjectWithID:mObjectID error:nil];
return item;
}
- (id)outlineView:(NSOutlineView *)anOutlineView persistentObjectForItem:(id)item
{
NSManagedObject *object = [item representedObject];
NSManagedObjectID *objectID = [object objectID];
return [[objectID URIRepresentation] absoluteString];
}
Any ideas? Thanks.
EDIT:
I have a clue! The problem is maybe that the tree controller has not prepared its content on time. The methods applicationDidFinishLaunching, outlineView:persistentObjectForItem etc. are being be executed before the data has loaded or rather the NSOutlineView hasn't finished initializing yet. Any ideas how to solve this?
I've had the problem that my implementation of -outlineView:itemForPersistentObject: was not called at all. It turns out that this method is called when either "autosaveExpandedItems" or "autosaveName" is set.
My solution was to set both properties in Code and NOT in InterfaceBuilder. When i set the properties after the delegate is assigned, the method gets called.
I got this to work - you need to return the corresponding tree node instead of "just" its represented object.
In itemForPersistentObject:, instead of return item; you need return [self itemForObject:item inNodes:[_treeController.arrangedObjects childNodes]];
with
- (id)itemForObject:(id)object inNodes:(NSArray *)nodes {
for (NSTreeNode *node in nodes) {
if ([node representedObject] == object)
return node;
id item = [self itemForObject:object inNodes:node.childNodes];
if (item)
return item;
}
return nil;
}
where _treeController is the NSTreeController instance that you use to populate the outline view.
Expanding on Karsten's solution:
The method -outlineView:itemForPersistentObject: gets called after doing what Karsten suggests, but ONLY if you also set the datasource before setting the delegate.
So if Karsten's answer doesn't seem to work, check where your datasource is set and adjust accordingly.
(wanted to write this as a comment but I'm not allowed due to my newbie status ...)
Swift 5 answer
Karsten is right, itemForPersistentObject must return a NSTreeNode.
Here is a Swift 5 version of the solution:
// This method should return a NSTreeNode object
func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {
guard let uriAsString = object as? String,
let uri = URL(string: uriAsString) else { return nil }
if let psc = self.managedObjectContext.persistentStoreCoordinator,
let moID = psc.managedObjectID(forURIRepresentation: uri),
let group = self.managedObjectContext.object(with: moID) as? MyGroupEntity,
let nodes = self.expensesTreeController.arrangedObjects.children {
return self.findNode(for: group, in: nodes)
}
return nil
}
/// Utility method to find the corresponding NSTreeNode for a given represented object
private func findNode(for object: NSManagedObject, in nodes: [NSTreeNode]) -> NSTreeNode? {
for treeNode in nodes {
if (treeNode.representedObject as? NSManagedObject) === object {
return treeNode
}
}
return nil
}
I never got this working.
This is my current way of doing it:
First, I added an attribute "isExpanded" and saved for each node the status in the database.
Second, I expand the nodes when my treeController has prepared its content.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[treeSectionController addObserver:self
forKeyPath:#"content"
options:0
context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == treeSectionController) {
NSArray *sectionArray = [[treeSectionController arrangedObjects] childNodes];
for (NSTreeNode *node in sectionArray) {
if([[node representedObject] isExpandedValue]) {
[outlinePilesView expandItem:node];
}
}
[treeSectionController removeObserver:self forKeyPath:#"content"];
}
}
Wow! 6 years later and this is still causing headaches.
I couldn't get this working initially, even with Karsten's helpful solution re setting autoSaveName & autosaveExpandedItems in code; itemForPersistentObject was still being called before the outlineView was populated. The solution for me, whilst not very elegant, was to set a delay of .5 seconds before setting autosaveExpandedItems & autoSaveName. The half second delay in my app is not noticeable. I used Vomi's code as well. Delegate and dataSource are set in IB bindings. Here's full solution:
override func viewDidLoad() {
super.viewDidLoad()
let _ = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { (timer) in
self.keywordsOutlineView.autosaveExpandedItems = true
self.keywordsOutlineView.autosaveName = "KeywordsOutlineView"
timer.invalidate()
}
}
func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? {
if let node = item as? NSTreeNode {
if let object = node.representedObject as? FTKeyword {
return object.objectID.uriRepresentation().absoluteString
}
}
return nil
}
// This method should return a NSTreeNode object
func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {
if outlineView == keywordsOutlineView {
guard let uriAsString = object as? String,
let uri = URL(string: uriAsString) else { return nil }
if let psc = self.managedObjectContext.persistentStoreCoordinator,
let moID = psc.managedObjectID(forURIRepresentation: uri),
let group = self.managedObjectContext.object(with: moID) as? FTKeyword,
let nodes = self.keywordsTreeController.arrangedObjects.children {
return self.findNode(for: group, in: nodes)
}
return nil
}
return nil
}
/// Utility method to find the corresponding NSTreeNode for a given represented object
private func findNode(for object: NSManagedObject, in nodes: [NSTreeNode]) -> NSTreeNode? {
for treeNode in nodes {
if (treeNode.representedObject as? NSManagedObject) === object {
return treeNode
}
}
return nil
}

How to write init method in Swift?

I want to write an init method in Swift. Here I initialize an NSObject class in Objective-C:
-(id)initWithNewsDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self) {
self.title = dictionary[#"title"];
self.shortDescription = dictionary[#"description"];
self.newsDescription = dictionary[#"content:encoded"];
self.link = dictionary[#"link"];
self.pubDate = [self getDate:dictionary[#"pubDate"]];
}
return self;
}
How can I write this method in Swift ?
that could be good bases for your class, I guess:
class MyClass {
// you may need to set the proper types in accordance with your dictionarty's content
var title: String?
var shortDescription: String?
var newsDescription: String?
var link: NSURL?
var pubDate: NSDate?
//
init () {
// uncomment this line if your class has been inherited from any other class
//super.init()
}
//
convenience init(_ dictionary: Dictionary<String, AnyObject>) {
self.init()
title = dictionary["title"] as? NSString
shortDescription = dictionary["shortDescription"] as? NSString
newsDescription = dictionary["newsDescription"] as? NSString
link = dictionary["link"] as? NSURL
pubDate = self.getDate(dictionary["pubDate"])
}
//
func getDate(object: AnyObject?) -> NSDate? {
// parse the object as a date here and replace the next line for your wish...
return object as? NSDate
}
}
advanced-mode
I would like to avoid to copy-pand-paste the keys in a project, so I'd put the possible keys into e.g. an enum like this:
enum MyKeys : Int {
case KeyTitle, KeyShortDescription, KeyNewsDescription, KeyLink, KeyPubDate
func toKey() -> String! {
switch self {
case .KeyLink:
return "title"
case .KeyNewsDescription:
return "newsDescription"
case .KeyPubDate:
return "pubDate"
case .KeyShortDescription:
return "shortDescription"
case .KeyTitle:
return "title"
default:
return ""
}
}
}
and you can improve your convenience init(...) method like e.g. this, and in the future you can avoid any possible mistyping of the keys in your code:
convenience init(_ dictionary: Dictionary<String, AnyObject>) {
self.init()
title = dictionary[MyKeys.KeyTitle.toKey()] as? NSString
shortDescription = dictionary[MyKeys.KeyShortDescription.toKey()] as? NSString
newsDescription = dictionary[MyKeys.KeyNewsDescription.toKey()] as? NSString
link = dictionary[MyKeys.KeyLink.toKey()] as? NSURL
pubDate = self.getDate(dictionary[MyKeys.KeyPubDate.toKey()])
}
NOTE: that is just a raw idea of how you could do it, it is not necessary to use conveniece initializer at all, but it looked obvious choice regarding I don't know anything about your final class – you have shared one method only.
class myClass {
var text: String
var response: String?
init(text: String) {
self.text = text
}
}
See Swift: Initialization
Do not need for call this method from other class it will get called automatically
override init()
{
super.init()
// synthesize.delegate = self
// println("my array elements are \(readingData)")
}
try:
initWithDictionary(dictionary : NSDictionary) {
init()
self.title = "... etc"
}
Source:
https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

How to resume a CAAnimation after coming back from multitasking

in my app i have an array of CALayer that I have animated along a bezierPath. When I close and reopen the app my layers are not animating and not in the same position as before closing the app. I have implemented two methods, pauseLayer and resumeLayer that works when I trigger them with two buttons inside my app but they won't work after closing the app. The code is the following
- (void)pauseLayers{
for(int y=0; y<=end;y++)
{
CFTimeInterval pausedTime = [car[y] convertTime:CACurrentMediaTime() fromLayer:nil];
car[y].speed = 0.0;
car[y].timeOffset = pausedTime;
standardUserDefaults[y] = [NSUserDefaults standardUserDefaults];
if (standardUserDefaults[y]) {
[standardUserDefaults[y] setDouble:pausedTime forKey:#"pausedTime"];
[standardUserDefaults[y] synchronize];
}
NSLog(#"saving positions");
}
}
-(void)resumeLayers
{
for(int y=0; y<=end;y++)
{
standardUserDefaults[y] = [NSUserDefaults standardUserDefaults];
car[y].timeOffset = [standardUserDefaults[y] doubleForKey:#"pausedTime"];
CFTimeInterval pausedTime = [car[y] timeOffset];
car[y].speed = 1.0;
car[y].timeOffset = 0.0;
car[y].beginTime = 0.0;
CFTimeInterval timeSincePause = [car[y] convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
car[y].beginTime = timeSincePause;
}
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
mosquitosViewController *mvc = [[mosquitosViewController alloc] init];
[mvc pauseLayers];
}
The problem with what you are trying to do above is that you are creating a completely new instance of your view controller, which is not the one that was showing onscreen. That's why nothing happens when you send the pauseLayers message.
What you should do is register to receive notifications for when your app goes to and comes from the background and call the appropriate methods (pauseLayers and resumeLayers) when that notification arrives.
You should add the following code somewhere in your mosquitosViewController implementation (I usually do so in viewDidLoad):
// Register for notification that app did enter background
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(pauseLayers)
name:UIApplicationDidEnterBackgroundNotification
object:[UIApplication sharedApplication]];
// Register for notification that app did enter foreground
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(resumeLayers)
name:UIApplicationWillEnterForegroundNotification
object:[UIApplication sharedApplication]];
I write a Swift 4 version extension based on #cclogg and #Matej Bukovinski answers from this thread. All you need is to call layer.makeAnimationsPersistent()
Full Gist here: CALayer+AnimationPlayback.swift, CALayer+PersistentAnimations.swift
Core part:
public extension CALayer {
static private var persistentHelperKey = "CALayer.LayerPersistentHelper"
public func makeAnimationsPersistent() {
var object = objc_getAssociatedObject(self, &CALayer.persistentHelperKey)
if object == nil {
object = LayerPersistentHelper(with: self)
let nonatomic = objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
objc_setAssociatedObject(self, &CALayer.persistentHelperKey, object, nonatomic)
}
}
}
public class LayerPersistentHelper {
private var persistentAnimations: [String: CAAnimation] = [:]
private var persistentSpeed: Float = 0.0
private weak var layer: CALayer?
public init(with layer: CALayer) {
self.layer = layer
addNotificationObservers()
}
deinit {
removeNotificationObservers()
}
}
private extension LayerPersistentHelper {
func addNotificationObservers() {
let center = NotificationCenter.default
let enterForeground = NSNotification.Name.UIApplicationWillEnterForeground
let enterBackground = NSNotification.Name.UIApplicationDidEnterBackground
center.addObserver(self, selector: #selector(didBecomeActive), name: enterForeground, object: nil)
center.addObserver(self, selector: #selector(willResignActive), name: enterBackground, object: nil)
}
func removeNotificationObservers() {
NotificationCenter.default.removeObserver(self)
}
func persistAnimations(with keys: [String]?) {
guard let layer = self.layer else { return }
keys?.forEach { (key) in
if let animation = layer.animation(forKey: key) {
persistentAnimations[key] = animation
}
}
}
func restoreAnimations(with keys: [String]?) {
guard let layer = self.layer else { return }
keys?.forEach { (key) in
if let animation = persistentAnimations[key] {
layer.add(animation, forKey: key)
}
}
}
}
#objc extension LayerPersistentHelper {
func didBecomeActive() {
guard let layer = self.layer else { return }
restoreAnimations(with: Array(persistentAnimations.keys))
persistentAnimations.removeAll()
if persistentSpeed == 1.0 { // if layer was playing before background, resume it
layer.resumeAnimations()
}
}
func willResignActive() {
guard let layer = self.layer else { return }
persistentSpeed = layer.speed
layer.speed = 1.0 // in case layer was paused from outside, set speed to 1.0 to get all animations
persistAnimations(with: layer.animationKeys())
layer.speed = persistentSpeed // restore original speed
layer.pauseAnimations()
}
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSLog(#"1");
mosquitosViewController *mvc = [[mosquitosViewController alloc] init];
[mvc pauseLayers];
/*
Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
*/
}
See my answer to this post for details on how to restart an animation after multitasking:
Restoring animation where it left off when app resumes from background