I converted my project to Swift 3, I am having trouble with the following piece of code, it seems that this ( >>>-) is no longer used in Swift 3. What does >>>- actually mean? and how to use it in Swift 3?
fileprivate func addImageToView(_ view: UIView, image: UIImage?) -> UIImageView? {
guard let image = image else { return nil }
let imageView = Init(UIImageView(image: image)) {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.alpha = 0
}
view.addSubview(imageView)
// add constraints
[NSLayoutAttribute.left, .right, .top, .bottom].forEach { attribute in
(view, imageView) >>>- { $0.attribute = attribute }
}
imageView.layoutIfNeeded()
return imageView
}
The >>>- returns a value. Before Swift 3, the compiler did not warn you if you did not assign the return value of a function or method to anything. Starting in Swift 3, you will get an error if the return value of a function or method (including operators) is unused. A library author can fix this by adding the #discardableResult annotation, but in the meantime you will have to change that line of code to:
let _ = (view, imageView) >>>- { $0.attribute = attribute }
Despite the fact this answer can solve the issue in your side, there still a need to the author of the library to make some changes on his side.
The problem is because swift removed completely the var from function parameters and somehow that change is affecting the param you're receiving in the closure. (It seem a bug to me). You need to figure it out what is the type of the inout param the library is passing to the operator closure, and declare it as inout in your call. let's say that type is Constraint (this might not be the same as the library), then on you:
(view, imageView) >>>- { (i : inout Constraint) in
i.attribute = attribute
}
But again, the author might still need to make some changes in the operator implementation as well.
Related
I have created a Document-based application that should save an NSAttributedString with a image into a package. I ran the application and added a image to the text view and saved it. When I opened the file, a dialog box said "the document 'x' could not be opened" and this was printed to the console:
[Layout] Detected missing constraints for <NSTextField: 0x100b3f480>. It cannot be placed because there are not enough constraints to fully
define the size and origin. Add the missing constraints, or set
translatesAutoresizingMaskIntoConstraints=YES and constraints will be generated for you. If this view is laid out manually on macOS 10.12 and later,
you may choose to not call [super layout] from your override. Set a breakpoint on DETECTED_MISSING_CONSTRAINTS to debug. This error will only be logged once.
Document.swift:
enum CookRecipesFileNames : String {
case notes = "Notes.rtfd"
}
class Document: NSDocument {
var documentFileWrapper = FileWrapper(directoryWithFileWrappers: [:])
var popover : NSPopover?
var notes : NSAttributedString = NSAttributedString()
...
override class func autosavesInPlace() -> Bool {
return true
}
override func fileWrapper(ofType typeName: String) throws -> FileWrapper {
let notesRTFdata = try self.notes.data(from: NSRange(0..<self.notes.length), documentAttributes: [NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType])
if let oldTextFileWrapper = self.documentFileWrapper.fileWrappers?[CookRecipesFileNames.notes.rawValue] {
self.documentFileWrapper.removeFileWrapper(oldTextFileWrapper)
}
self.documentFileWrapper.addRegularFile(withContents: notesRTFdata, preferredFilename: CookRecipesFileNames.notes.rawValue)
return self.documentFileWrapper
}
override func read(from fileWrapper: FileWrapper, ofType typeName: String) throws {
guard let documentNotesData = fileWrappers[CookRecipesFileNames.notes.rawValue]?.regularFileContents else {
throw err(.cannotLoadNotes)
}
guard let documentNotes = NSAttributedString(rtfd: documentNotesData, documentAttributes: nil) else {
throw err(.cannotLoadNotes)
}
self.documentFileWrapper = fileWrapper
self.notes = documentNotes
}
}
Any help would be appreciated!
The warning 'Detected missing constraints...' is a Build Warning, telling you that you have added constraints to a view, but not enough to determine x and y coords, and height and width for the view. If you add no constraints in IB (an option here is to remove them all), Xcode will tell the app to use the exact position and dimensions used in IB. If you add ANY constraints to a view, you must add enough to determine both coordinates and both dimensions. If you want to keep your constraints (this is the second option), go to IB, and look for the yellow or red warning error, at the top left:
This will give you a list of missing and conflicting restraints in the View Controller.
Let's say we have two instances of UILabel that, to us, are equivalent:
let label1 = UILabel()
label1.text = "Hello world"
let label2 = UILabel()
label2.text = "Hello world"
Let's say the two instances are in an array of type [UIView]:
let views: [UIView] = [label1, label2]
Is there any way to perform an equality check that would find these two instances of UIView to be equivalent without knowing which type they are ahead of time (and therefore which common properties to compare on)?
(Any way to use the fact that these instances are both have a dynamicType of UILabel and dynamically run through the key/value properties of the UILabel class and compare each value that can be compared?)
What's happening:
label1 == label2 // false
views[0] == views[1] // false
What's desired:
areTheSame(label1, label2) // true
areTheSame(views[0], views[1]) // true
We are looking for a way to compare two separate instances, so we can't use ===.
Swift has no reflection so this is not possible. We can't even get a list of attributes.
Also note that for many types there is no definition of equality. Even comparing two floating point values is a problem.
Exactly for this reason we have the Equatable protocol. If you want to compare types, define equality on them. That equality can then go as deep as needed, without the need for any dynamic (unsafe) behavior.
Just to explain another corner case, for example, on UILabel there are some properties that you definitely don't want to compare, namely things like nextResponder or superview. Trying to deep compare those properties would actually end up in a loop. Usually it's not possible to compare two objects for equality without knowing exactly what should and what should not be compared.
Something like this should work:
let views: [UIView] = [label1, label2]
func areTheSameLabel(view1: UIView, _ view2: UIView) -> Bool {
guard let label1 = view1 as? UILabel,
let label2 = view2 as? UILabel else { return false }
return label1.text = label2.text
}
print(areTheSameLabel(label1, label2)) // Should print "true"
print(areTheSameLabel(views[0], views[1])) // Should print "true"
In response to your comments, I think the best avenue is to create a protocol:
protocol ViewEquatable {
var backgroundColor: UIColor? { get set }
var text: String? { get set }
// add any other properties you want to compare here...
}
Then write a function to compare them:
func ==<T: ViewComparable>(lhs: T, rhs: T) -> Bool {
return (lhs.backgroundColor == rhs.backgroundColor) &&
(lhs.text == rhs.text) &&
// whatever other comparison tests you need go here...
}
Something like that is probably your best option, though your requirements are too vague to give a complete answer...
In my application I'm using the as operator to check what type of UI element I'm working with. I ran into an issue where UILabel is successfully checking as a view.
let label = UILabel()
label.text = "some text"
if let myLabel = label as? UIView {
print("its a view ") // succeeds
}
I also receive the warning that states:
warning: Conditional cast from 'UILabel' to 'UIView' always succeeds.
Question
Is there a way to add a constraint to this as check that will cause this check to fail as expected?
Try this:
if label.isMemberOfClass(UIView.self) {
print("its a view ") // succeeds
}
isMemberOfClass checks for UIView and no classes that inherit from it
Here's another approach, which is the one I needed for my particular situation as I was attempting to check a generic (T). Since I didn't specify that in the question, I'll won't accept this as the answer, but here is the solution:
if let label = obj as? UILabel where label.isMemberOfClass(UILabel.self) {
// ...
}
if let view = obj as? UIView where view.isMemberOfClass(UIView.self) {
// ...
}
So, I made a generic function in a structure which has some static methods for helping to create customized UIButtons and so on. So I did this code:
static func createAlertPicker<T: UIViewController where T: UIPickerViewDelegate, T: UIPickerViewDataSource>(#title: String, inout forPicker picker: UIPickerView, viewController: T) -> UIAlertController {
let alert = UIAlertController(title: title, message: "\n\n\n\n\n\n\n\n\n\n", preferredStyle: .Alert)
alert.view.tintColor = data.backgroundColor
picker = createPickerViewWithFrame(CGRectZero, delegate: viewController, dataSource: viewController, backgroundColor: UIColor.clearColor(), addToView: alert.view)
picker.frame = CGRect(x: 2, y: 70, width: 266, height: 162)
return alert
}
I don't get an error doing this but when calling this method in a ViewController like so:
let alert = CreatorClass.createAlertPicker(title: "select sortage", forPicker: &self.pickerView!, viewController: self)
I get a crazy error telling me Type 'UIViewController' does not conform to protocol 'UIPickerViewDelegate'. Although it is implemented, that's the ViewController's declaration:
class PlayerDetails:UIViewController, UITableViewDataSource, UITableViewDelegate, UIPickerViewDelegate, UIPickerViewDataSource
By the way, this problem only occurs in one ViewController, I'm calling it several times. Maybe it should be mentioned that this line of code (let alert = ...) is not even compiled in the first place.
I really don't understand this. Thank for any help ! :]
Yeah that error is a total lie.
The problem is with the middle parameter: &self.pickerView!.
self.pickerView is an optional. Your optional contains a reference, but unwrapping that reference passes back you back a fresh copy of the reference by value. You don’t get access to the original reference inside the optional. So when you call !, you get an immutable value. You can’t change it or assign to it, and that means you can’t pass it as an inout parameter.
Here’s a simpler example:
let i: Int? = 5
func f(inout i: Int) { i = 6 }
f(&i!) // error: 'Int' is not convertible to '#lvalue inout $T3’
This is the compiler saving you from a potentially very confusing runtime bug – if the value were passed in and changed, it would make no difference to the value you actually intended to change. Only the temporary copy would have been changed.
It might be a bit confusing because classes are reference types so you’re not used to thinking about them in value terms. But references themselves are values. What you are getting out of the unwrap is a copy of the reference, not a copy of the thing referred to.
If you change your call to something like the following, it should work:
if var picker = self.pickerView {
let alert = CreatorClass.createAlertPicker(title: "select sortage", forPicker: &picker, viewController: self)
// don’t forget to assign the value back...
self.pickerView = picker
}
This version also has the benefit of not exploding in flames if you’ve ever forgotten to set forPicker to be a value before you force unwrap it.
But if all you are using the inout for is to return a new picker (doesn’t look in your createAlertPicker like you use the value passed in, only assign to it), then why not ditch the inout and make the function return a pair of values:
static func createAlertPicker
<T: UIViewController where T: UIPickerViewDelegate>
(#title: String, viewController: T)
// return a tuple
-> (UIPickerView,UIAlertController) {
// etc…
var picker = createPickerViewWithFrame(CGRectZero, delegate: viewController, dataSource: viewController, backgroundColor: UIColor.clearColor(), addToView: alert.view)
picker.frame = CGRect(x: 2, y: 70, width: 266, height: 162)
return (picker, alert)
}
let (picker, alert) = CreatorClass.createAlertPicker(title: "select sortage”, viewController: self)
self.pickerView = picker
I've run into an odd case when trying to check a return value, and I'm wondering how to do this "properly", in the Swift sense.
I have an NSStatusItem (named item), and I'm trying to assign the NSStatusItem an NSImage. When I make the NSImage, since I pass it a string value for the image name, I want to make sure that the NSImage is actually valid (what if I mistype the image name string?).
The first thing I tried was this:
if let image: NSImage? = NSImage(named: "CorrectIconName") {
item.image = image
}
But this gives the error "Bound value in a conditional binding must be of Optional type". I thought that saying image: NSImage? made it clear that it was Optional, but I guess not.
I changed that to this:
let image: NSImage? = NSImage(named: "CorrectIconName")
if image {
item.image = image
}
Which works totally fine. But I don't get why this works, while the first example doesn't. It seems more-or-less to be the exact same thing. And since the first one didn't compile, I thought I'd try some other routes...
Since NSImage(named:) does return an NSImage and not an NSImage?, I thought I'd see what happened if I assigned the return value of the constructor directly to item:
item.image = NSImage(named: "CorrectIconName")
Which works, but doesn't allow for the error checking I want to do. If the string is wrong, the NSStatusItem gets nil for an image, which leads to me having an invisible status bar item.
Next, I tried this:
let image: NSImage = NSImage(named: "CorrectIconName")
if image {
item.image = image
}
But this gives the error "Type 'NSImage' does not confirm to protocol 'LogicValue'", which I guess means you aren't allowed to check if it's nil or not with an if statement.
However, you can check whether it is nil by doing the following:
let image: NSImage = NSImage(named: "CorrectIconName")
if image != nil {
item.image = image
}
So, here's the question: how exactly is one supposed to check a return value if it isn't Optional?
It actually is Optional, the compiler just isn't showing it to you.
In Apple's documentation about working with Objective-C objects, it says that all objects imported from Objective-C APIs are actually implicitly unwrapped optionals (like we would manually declare with !):
In some cases, you might be absolutely certain that an Objective-C
method or property never returns a nil object reference. To make
objects in this special scenario more convenient to work with, Swift
imports object types as implicitly unwrapped optionals. Implicitly
unwrapped optional types include all of the safety features of
optional types. In addition, you can access the value directly without
checking for nil or unwrapping it yourself. [source]
Unfortunately, the compiler/syntax checker doesn't treat them as such. Therefore, the correct way of checking would be to declare image as the type that the NSImage initializer is actually returning, an implicitly unwrapped optional NSImage:
let image: NSImage! = NSImage(named: "CorrectIconName")
if image {
// do something
}
Alternate method (via #vacawama):
if let image = NSImage(named: "CorrectIconName") as NSImage! {
// do something
}
There is a bit of a mismatch between Objective-C initializers which may return nil and Swift's init semantics. If you invoke SomeObject(...) it's defined by the language to create an instance of SomeObject NOT SomeObject?
There are a few examples of initializers in the Cocoa/Foundation frameworks that, from a Swift perspective, generate an NSSomething? not the implied NSSomething
There are myriad way for Apple to address this without compromising the Swift type system, but until they do, there are several ways for you to "Do the Right Thing."
Re-wrap the object in a conditional - this works but is clunky:
var maybeImage: NSImage? = NSImage(named: "CorrectIconName")
if let image = maybeImage {
}
Better is to make your own function for loading NSImage
func loadImageNamed(name: String) -> NSImage? {
return NSImage(named: name)
}
func doSomethingWithAnImage() {
if let image = loadImageNamed( "CorrectIconName") {
....
}
}
Alternatively you can extend NSImage.
extension NSImage {
class func imageWithName(name: String) -> NSImage? {
return NSImage(named: name)
}
}
Now you can do the natural thing.
if let image = NSImage.imageWithName("CorrectIconName") {
}
This seems to be the right thing for the Swift/Cocoa API and I hope Apple does something similar in the future.
Unless I'm missing something completely obvious, this is more a matter of opinion. You should probably be storing the return in a variable and then checking the contents of that variable. I think this is mostly because what if that variable ended up not being declared if the return was optional? The if statement would have a fit!
This is pertaining to the first part of your question, which also seems like the rest of your question, but I might be confused...
I had similar problem in IOS and solved it using the ?-operator:
if let im = UIImage(named: "Meetup.png")? {
imageLayer.contents = im.CGImage
}
Without the the ? it would not enter the if branch, but when using ? it does !