I've succeeded in incorporating SDWebImage (written in Objective-C) with my Swift project - but its still acting a bit funny. Specifically, its giving me an error in the if statement inside the following Closure:
let completionBlock: SDWebImageCompletionBlock! = {
(image:UIImage!, error: NSError!, cacheType:SDImageCacheType, imageURL:NSURL!) -> Void in
if (image && cacheType == SDImageCacheType.SDImageCacheTypeNone) {
cell.productImageView.alpha = 0.0
UIView.animateWithDuration(1.5, animations: {
cell.productImageView.alpha = 1.0
})
}
}
cell.productImageView.sd_setImageWithURL(imageURL!, placeholderImage:UIImage(named:"Icon120pix.png"), completed: completionBlock)
The error I'm getting on that if statement is: Use of unresolved identifier 'SDImageCacheTypeNone'
This makes no sense because SDImageCacheTypeNone is one of the values defined in the SDImageCacheType typedef.
By the way, if I take that if statement out and leave just the statements inside it, everything works just fine.
So any ideas what I might be doing wrong here?
Have a look at this document in the Enumerations section for a more in depth explanation as to why Swift displays Objective-C enums differently.
Apple Swift Documents
In your code example the way to solve the problem is to use:
SDImageCacheType.None
Instead of
SDImageCacheType.SDImageCacheTypeNone
Related
I was asked to migrate a rather large app to Swift 2. The compiler keeps throwing segmentation fault: 11 errors for one function, present in different modules of the app's logic (only difference being variables used):
func loadMoreContent() {
if let collection = self.ratingsCollection where collection.identifier != 0,
let totalEntries = collection.totalEntries,
let objects = self.ratings?.count where objects < totalEntries {
self.ratingsCollection = nil
collection.nextPage().onSuccess { (value) in
if let collection = value as? Collection<Rating> {
self.ratingsCollection = collection
} else {
self.ratingsCollection = Collection<Rating>(identifier: 0)
}
}.onFailure { error in
self.ratingsCollection = Collection<Rating>(identifier: 0)
}
}
}
Here are the errors themselves:
1. While type-checking 'loadMoreContent' at (path redacted).swift:46:3
2. While type-checking expression at [(path redacted).swift:54:9 - line:64:9]
RangeText="collection.nextPage().onSuccess { (value) in
if let collection = value as? Collection<Rating> {
self.ratingsCollection = collection
} else {
self.ratingsCollection = Collection<Rating>(identifier: 0)
}
}.onFailure { error in
self.ratingsCollection = Collection<Rating>(identifier: 0)
}"
3. While loading members for declaration 0x7fdda42ea2b0 at <invalid loc>
4. While deserializing 'producer' (FuncDecl #340)
Does anyone have any idea what can be wrong with this function at first glance? I should add it compiles with no changes in Xcode 6 / Swift 1.2.
This is a hair pulling error especially common in XCode7.
Occasionally the usual XCode stupid bug protocol (clean, XCode Restart, clean, build) fixes it. However, often it is due to one or more offending lines of code. This doesn't necessarily mean there is a bug in the code, either!
So, before restarting, it is sometimes useful to undo recent changes sequentially and trying to build as you go along. If any of your dependencies or frameworks have been updated since your last successful build, these could be a likely candidate.
There are a couple things that seem to produce this error fairly regularly. So please add to this list concisely if you can isolate specific issues that CONSISTENTLY cause errors for you:
1) String concatenation using the plus operator in calls to methods that use autoclosures (found in calls to XCGLogger):
public func myFunc(#autoclosure closure: () -> String?){
// do something
}
someInstance.myFunc("Hi " + nameStr + "!")
2) failure to call super.init() from subclass especially when super class is using a default initializer (you haven't explicitly created your own init)
3) Accidentally using a single equals sign to test for equality (using = instead of == ) especially in complex statement such as in this answer.
I'm very new to swift, but proficient in other languages like Java, JavaScript, C, ... I'm lost with Swift syntax when it comes to create expressions. Look at this basic example where I just try to find out if one string is contained into another by calling String.rangeOfString that returns an Optional Range (Range?)
This works as expected:
let LEXEMA:String="http://"
let longUrl:String="http://badgirls.many/picture.png"
let range=longUrl.rangeOfString(LEXEMA);
if (range? != nil) {
// blah
}
Now I'm trying to combine the expression inside the if, something like:
if (longUrl.rangeOfString(LEXEMA)? !=nil) {
// blah
}
But I always get syntax errors, the above yields a "Expected Separator" and can't understand why. Done some more tests:
if (absolutePath.rangeOfString(URL_LEXEMA) !=nil) { }
Expected Separator before "!"
if absolutePath.rangeOfString(URL_LEXEMA) !=nil { }
Braced block of statements is an unused closure
What am I doing wrong?
If you’re coming from other like Java, you might be thinking of optionals like pointers/references, and so used to equating them to nil and if non-nil, using them. But this is probably going to lead to more confusion. Instead, think of them like a container for a possible result, that you need to unwrap to use. if let combines the test and unwrapping operation.
With this in mind, here’s how you could adapt your code:
let LEXEMA: String="http://"
let longUrl: String="http://badgirls.many/picture.png"
if let range = longUrl.rangeOfString(LEXEMA) {
// use range, which will be the unwrapped non-optional range
}
else {
// no such range, perhaps log an error if this shouldn’t happen
}
Note, that ? suffixing behaviour you were using changes in Swift 1.2 so even the code in your question that compiles in 1.1 won’t in 1.2.
It’s possible that sometimes you are whether there was a value returned, but you don’t actually need that value, just to know it wasn’t nil. In that case, you can compare the value to nil without the let:
if longUrl.rangeOfString(LEXEMA) != nil {
// there was a value, but you don't care what that value was
}
That said, the above is probably better expressed as:
if longUrl.hasPrefix(LEXEMA) { }
For starters:
You don't need parenthesis with if statements unless you have nested parenthetical subexpressions that require it.
You don't need to specify the type on the left side of the = of a let or var declaration if Swift can figure it out from the right side of the =. Very often Swift can figure it out, and you can tell that Swift can figure it out, so you can avoid that redundant clutter.
You do need to specify the type if Swift cannot figure out the type from
the right side. Example:
For example, consider the following lines:
let LEXEMA = "http://"
let longUrl = "http://badgirls.many/picture.png"
Swift can figure out that they're strings.
Similarly for this function or class that returns a UIView:
var myView = ViewReturningClassOrFunc()
Consider this:
#IBOutlet var myView : UIView!
In the above line, Swift cannot figure out ahead of time it will be assigned a UIView, so you have to provide the type. By providing a ! at the end you've made it an implicitly unwrapped optional. That means, like ?, you're indicating that it can be nil, but that you are confident it will never be nil at the time you access it, so Swift won't require you to put a ! after it when you reference it. That trick is a time saver and big convenience.
You should NOT add the ? to the line:
if (longUrl.rangeOfString(URL_LEXEMA) !=nil) {
As another answer pointed out, you're missing the let.
if let longUrl.rangeOfString(URL_LEXEMA) {
println("What do I win? :-)")
}
swift is case sensitive language. you need to check about whitespaces as well
if longUrl.rangeOfString(LEXEMA) != nil {
//your condition
}
there should be space between statement != nil
Just add a space between != and nil like:
if longUrl.rangeOfString(LEXEMA) != nil {
// blah
}
I tested your code in playground, an error of Expected ',' separator reported.
And do not forget the rules that 1s and 0s and Airspeed Velocity said.
I am not totally sure if this the right place to post this, as it is more a pitfall I have found than a question I would ask. (Although I would be very interested if someone could explain the reason why this happens.)
So in my Swift iOS-app I had to use Objective-C Arrays for sorting. I knew for a fact that the NSMutableArray called results would contain only MyObjects. Thus, after sorting I cast it to [MyObject] like this to save it in the variable myArrayVar : [MyObject].
myArrayVar = (results as NSArray) as [MyObject]
This worked fine until I tested it on a release build. There it crashed. What I had to do to fix the crash was this:
if let results = results as Any as? NSArray {
if let results = results as? [MyObject] {
myArrayVar = results
} else { NSLog("the impossible happened.") }
} else { NSLog("the impossible happened.") }
Now we can see that this version cannot crash when the casting goes wrong whereas the first version would. However, the cast does not go wrong as I could verify by never seeing the log message.
So what might be the difference at runtime between these two versions of type casting? Whatever it is I have the feeling it might be a pitfall for others as well.
Update 3/23/2016 I just tested my original sample code below and it all compiles fine in XCode 7.3. Looks like XCTAssertNil was updated along the way to take an expression of type () throws -> Any? Therefore this question and answer may be no longer needed (except for a while with older versions of the compiler.)
I'm writing my first unit tests in XCode with XCTest. I'm unsure how one can take advantage of XCTAssertNil as it seems to only compile when using certain types. It appears it will work with optionals made from classes and built-in primitives, but not structs. How would one go about using this method?
For structs the compiler gives the following error (assuming 'SimpleStruct' is the name of your type):
'SimpleStruct' is not identical to 'AnyObject'
Here's a simple test class to illustrate some of the types that compile okay and other's that don't.
import Cocoa
import XCTest
struct SimpleStruct {
}
class SimpleClass {
}
class Tests: XCTestCase {
func testl() {
var simpleStruct:SimpleStruct? = nil;
var simpleClass:SimpleClass? = nil;
var i:Int? = nil;
var s:String? = nil;
var tuple:(Int,String)? = nil;
XCTAssertNil(simpleStruct); // compile error
XCTAssertNil(simpleClass); // OK
XCTAssertNil(i); // OK
XCTAssertNil(s); // OK
XCTAssertNil(tuple); // compile error
}
}
Update 3/23/2016 Updated for XCode 7.3 (however if you see my edit to the question, it would appear this workaround is no longer needed)
Here is a workaround. I created my own generic function:
func AssertNil<T>(#autoclosure expression: () -> T?, message: String = "",
file: StaticString = #file, line: UInt = #line) {
if (expression() != nil) {
XCTFail(message, file:file, line:line)
}
}
Doesn't seem like this should be necessary. Is this just a result of XCTest originally targeting Objective-C and not being updated/bridged enough for Swift yet?
Edit: I've done enough research to see that AnyObject can be used to represent any class but not structs. However, that then doesn't explain why the code in my original post compiles for Int types and String types. (I read somewhere else that Xcode may auto convert these to NSNumber and NSString for you which might explain why. See http://www.scottlogic.com/blog/2014/09/24/swift-anyobject.html and http://www.drewag.me/posts/swift-s-weird-handling-of-basic-value-types-and-anyobject. I'll try removing my import of Cocoa which imports Foundation to see what happens)
Edit2: XCTest also imports Foundation so I can't test what I wanted to. I could create my own methods and test this. For now, I assume that the auto-conversions are what are allowing the int and string optionals to compile. Seems like XCTest isn't quite ready for prime time with Swift.
Update 8/13/2015: Edited the function to be compatible with XCode 7 beta
I'm using the
loadItemForTypeIdentifier:options:completionHandler: method on an NSItemProvider object to extract a url from Safari via a Share extension in iOS 8.
In Objective-C, this code compiles and works:
[itemProvider loadItemForTypeIdentifier:(#"public.url" options:nil completionHandler:^(NSURL *url, NSError *error) {
//My code
}];
In Swift however, I'm getting "NSSecureCoding!' is not a subtype of 'NSURL" compile error when I try to do something similar:
itemProvider.loadItemForTypeIdentifier("public.url", options: nil, completionHandler: { (urlItem:NSURL, error:NSError!) in
//My code
})
If I add the bang to NSURL argument type as in NSURL! I get "Cannot convert the expression's type 'Void' to type 'Void'" compile error. And if I leave the default argument typed as NSSecureCoding!, it compiles, but the block/closure doesn't run.
What am I doing wrong?
You don't need to specify the types for urlItem or error as they can be inferred from the declaration of loadItemForTypeIdentifier:options:completionHandler. Just do the following:
itemProvider.loadItemForTypeIdentifier("public.url", options: nil, completionHandler: {
(urlItem, error) in
//My code
})
Even better, you can move the closure outside of the method call:
itemProvider.loadItemForTypeIdentifier("public.url", options: nil) {
(urlItem, error) in
//My code
}
This API makes use of reflection internally: it looks at the type of the block’s first parameter to decide what to return. This is dependent on Objective-C’s looser enforcement of the block signature compared to Swift — and therefore does not work in Swift. (Yes, still.) See this discussion on the developer forums.
I recommend filing a bug report with Apple and writing small Objective-C wrapper methods to read each type of data you need.
It’s possible I’ve overlooked something. If someone has found a neat way to make this work in Swift I’m keen to hear it.
The new API to use is canLoadObject and loadObject, use it as this:
if (itemProvider.canLoadObject(ofClass: NSURL.self)) {
print("==== attachment is URL")
itemProvider.loadObject(ofClass: NSURL.self, completionHandler:
{
(data, error) in
print("==== url object = \(data)")
})
}
The same can be used for UIImage
https://developer.apple.com/documentation/uikit/drag_and_drop/data_delivery_with_drag_and_drop
Just in case you or other ones still need a solution, it is simple. Just add a side variable with a cast to NSURL. Here it is:
itemProvider.loadItemForTypeIdentifier("public.url", options: nil) {
(urlItem, error) in
let url : NSURL = urlItem : NSURL
// Whatever you like to do with your new url
}