2016.08.17: Just upgraded to Xcode 8 beta 6, and lo and behold... have to do it all again...
I just pushed the new versions for SwifterLog (v0.9.12), SwifterSockets (v0.9.6), SwifterJSON (v0.9.10) and Swiftfire (v0.9.13) to github.
All of them upgrades to Swift 3 (beta), so only make the change if you are also working with Swift 3/Xcode 8 beta 3.
All in all I am rather pleased with the changes in Swift 3, they do make the code better. It took me about a week to change all of the above mentioned source code. Which is a lot, so yes, upgrading an existing code base to Swift 3 will cost you some. Worth it or not, that is something only you can decide. For open source projects like the above, it's a no-brainer of course.
So... on and forward to v1.0... :-)
Happy coding...
Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH
We don't get the world we wish for... we get the world we pay for.
Posts about stuff I encounter when programming in Swift. Lots of example code.
Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.
Showing posts with label SwifterLog. Show all posts
Showing posts with label SwifterLog. Show all posts
2016-08-06
2015-06-17
Using Drag & Drop with custom types from Swift
I wanted to use Drag & Drop from within my own application for my own custom types. I already have implemented drag & drop for sources outside my application as I have blogged about in this post. In another post I have documented the use of the pasteboard from within Swift.
So adding drag & drop when the source and destination are my own app should be easy right?
Well, no. I did not find this easy at all. But I did get it to work :)
First things first: The apple documentation (that I could find) is of little to no help at all. It is either outdated or it laks guidance. Besides, I must be the only one having trouble with this because there is almost nothing to find on the inet ... well, there is, but it is also outdated...
Ok, here is the deal:
First decide which type to drag & drop. For that type the NSPasteboardWriting and NSPasteboardReading protocols have to be adopted. Oh, and that type (class) must inherit from NSObject as well.
Like this:
In the above code I used my own SwifterJSON framework to encode and decode the data of MyData. If you would like to take a look at that, find the link on the left hand side of this blog.
Normally the propertyList in the pasteboard reading protocol will deliver NSData objects, but since I know that there will be a JSON string in there, I have also supplied the optional readingOptionsForType method so that the propertyList will be readable as a String when the init is called.
PS I have ignored error handling in init for brevity.
With the above code in place, we can now create MyData objects from the pasteboard like this:
The yellowish underlaid code is the important bit. I have left some of the older code in so that you see how this could all work together. Refer to the blogs I mentioned at the top to see more on that. You can find a link to the logging framework I use to the left of this blog. (Note: I always accept the drop, this is my own data type, so I better make sure all errors are fixed before shipping!)
Oh, lest I forget, we also need to register ourselves to receive the new type of drop: in awakeFromNb I added the following:
With this code in place we can accept drops for our own custom data on a pasteboard. All that is left is to provide for a source that generates the pasteboard information. I had to do this in a view, but it can also be done in a window (so I believe...). In my custom NSView child I added the dragging source protocol:
The only method that I needed was:
In this method I make clear that the drag & drop for the session that this view will begin is only available for internal drag & drop's. The data cannot be exported (dropped) outside my app.
To begin the drag & drop I added the following code to the mouseDown method:
The salient point is the code with the yellowish background. All the other code is only necessary for my application. beginDraggingSessionWithItems is the code that starts the drag operation. But it needs an array of NSDraggingItem(s). Thus the MyData objects are wrapped in a NSDraggingItem The NSDraggingItem can only wrap objects that implement the NSPasteboardWriting protocol.
While the above should get you up and running, it is not complete. You will probably want to add visual indicators of your own making to the drag & drop. This can be done (so I understand) through the NSDraggingItem
Happy coding...
Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH
We don't get the world we wish for... we get the world we pay for.
So adding drag & drop when the source and destination are my own app should be easy right?
Well, no. I did not find this easy at all. But I did get it to work :)
First things first: The apple documentation (that I could find) is of little to no help at all. It is either outdated or it laks guidance. Besides, I must be the only one having trouble with this because there is almost nothing to find on the inet ... well, there is, but it is also outdated...
Ok, here is the deal:
First decide which type to drag & drop. For that type the NSPasteboardWriting and NSPasteboardReading protocols have to be adopted. Oh, and that type (class) must inherit from NSObject as well.
Like this:
class MyData: NSObject, NSPasteboardWriting, NSPasteboardReading {
var eData: String!
var oData: String!
init(e: String, o: String) {
super.init() // Necessary for NSObject
oData = o
eData = e
}
private func jsonString() -> String {
let top = SwifterJSON.createJSONHierarchy()
top["eData"].stringValue = eData
top["oData"].stringValue = oData
return top.description
}
// MARK: - Pasteboard Writing Protocol
@objc func writableTypesForPasteboard(pasteboard: NSPasteboard!) -> [AnyObject]! {
return ["com.mycompany.MyApp.MyData"]
}
@objc func pasteboardPropertyListForType(type: String!) -> AnyObject! {
return jsonString()
}
// MARK: - Pasteboard Reading Protocol
init!(pasteboardPropertyList propertyList: AnyObject!, ofType type: String!) {
super.init()
if let jsonString = propertyList as? String {
let (topOrNil, errorOrNil) = SwifterJSON.createJsonHierarchyFromString(jsonString)
if let top = topOrNil {
oData = top["oData"].stringValue ?? "Error"
eData = top["eData"].stringValue ?? "Error"
} else {
return nil
}
} else {
return nil
}
}
static func readableTypesForPasteboard(pasteboard: NSPasteboard!) -> [AnyObject]! {
return ["com.mycompany.MyApp.MyData"]
}
static func readingOptionsForType(type: String!, pasteboard: NSPasteboard!) -> NSPasteboardReadingOptions {
return NSPasteboardReadingOptions.AsString
}
}
In the above code I used my own SwifterJSON framework to encode and decode the data of MyData. If you would like to take a look at that, find the link on the left hand side of this blog.
Normally the propertyList in the pasteboard reading protocol will deliver NSData objects, but since I know that there will be a JSON string in there, I have also supplied the optional readingOptionsForType method so that the propertyList will be readable as a String when the init is called.
PS I have ignored error handling in init for brevity.
With the above code in place, we can now create MyData objects from the pasteboard like this:
func performDragOperation(sender: NSDraggingInfo) -> Bool {
// Get the pasteboard
if let pboard = sender.draggingPasteboard() {
// Get the Y coordinate for the drop
let dropCoordinate = sender.draggingLocation()
// Check if the drop position is within any of our subviews.
if !pointIsInADocumentView(dropCoordinate) { return false }
// Offset the Y coordinate
let dropPoint = theView.convertPoint(dropCoordinate, toView: aSubView)
// Get the index for the drop
let dropIndex = lineIndexFromYCoordinate(dropPoint.y)
// Make sure that there are filenames in the pasteboard
if pboard.availableTypeFromArray([NSFilenamesPboardType]) == NSFilenamesPboardType {
// Get the filenames from the pasteboard
let files = filesFromPboard(pboard)
// Add the filenames to the datasource
insertLines(files, atIndex: dropIndex)
// Update the view
theView.needsDisplay = true
// The drop was accepted
return true
} else if pboard.availableTypeFromArray(["com.mycompany.MyApp.MyData"]) == "com.mycompany.MyApp.MyData" {
if var specs = pboard.readObjectsForClasses([MyData.self], options: nil) as? Array<MyData> {
dataModel.insertLines(fileSpecs, atIndex: dropIndex)
} else {
log.atLevelError(id: 0, source: "performDragOperation", message: "Could not read MyData from pasteboard")
}
// The drop was accepted
return true
}
}
// Still here, then something went wrong. The drop is not accepted.
return false
}
The yellowish underlaid code is the important bit. I have left some of the older code in so that you see how this could all work together. Refer to the blogs I mentioned at the top to see more on that. You can find a link to the logging framework I use to the left of this blog. (Note: I always accept the drop, this is my own data type, so I better make sure all errors are fixed before shipping!)
Oh, lest I forget, we also need to register ourselves to receive the new type of drop: in awakeFromNb I added the following:
// Add drag and drop support
registerForDraggedTypes([NSFilenamesPboardType, "com.mycompany.MyApp.MyData"])
class MyView: NSView, NSDraggingSource {...
func draggingSession(session: NSDraggingSession, sourceOperationMaskForDraggingContext context: NSDraggingContext) -> NSDragOperation {
switch context {
case .OutsideApplication: return .None
case .WithinApplication: return .Move
}
}
To begin the drag & drop I added the following code to the mouseDown method:
override func mouseDown(theEvent: NSEvent) {
// Get the mouse position from the event position
let mp = convertPoint(theEvent.locationInWindow, fromView: nil)
if (mp.x >= 0.0) && (mp.y >= 0.0) && (mp.x <= frame.width) && (mp.y <= frame.height) {
let mousePosition = CGPointMake(mp.x - MARGIN_BEFORE_LINE, mp.y)
// If the option key is down as well, this will be a drag & drop
if theEvent.modifierFlags.isSet(.AlternateKeyMask) {
// Find out if the mouse is down in a selection
let lineIndex = lineIndexFromYCoordinate(mousePosition.y)
if lineIndex >= dataSource.nofLines { return } // Ignore if the mouse down position is outside any of the lines
let line = dataSource.lineAtIndex(lineIndex)
let charIndex = line.indexForOffset(mousePosition.x)
let lines = dataSource.linesInBlokSelect(lineIndex, charIndex: charIndex, lineSource: dataSource.lineAtIndex)
var dragItems = Array<NSDraggingItem>()
for l in lines { dragItems.append(NSDraggingItem(pasteboardWriter: l)) }
beginDraggingSessionWithItems(dragItems, event: theEvent, source: self)
// Remove the original data from the datamodel
for l in lines { dataSource.removeLine(l) }
} else {
// Normal mouse down
mouseDownPosition = mousePosition
}
} else {
return
}
}
While the above should get you up and running, it is not complete. You will probably want to add visual indicators of your own making to the drag & drop. This can be done (so I understand) through the NSDraggingItem
Happy coding...
Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH
We don't get the world we wish for... we get the world we pay for.
2015-03-27
Using the Info.plist
Every application on OS-X has a Info.plist file. When writing a utility like SwifterLog I find it nicer if the log levels can be set in a plist rather than in code.
But then it is necessary to access the Info.plist's properties during initialisation. Luckily Apple has made this easy. I use the following code to achieve this:
The corresponding entries in the plist file look as follows:
As you can see, I have added a dictionary item "SwifterLog" to the Info.plist file, and in that dictionary the configuration items for SwifterLog.
Consequently, in the source code I first need to gain access to the Info.plist, then the SwifterLog dictionary and then to the configuration items.
Swift allows us to encapsulate this nicely by using the conditional assignment. Notice that this also ensures that when the Info.plist does not contain a SwifterLog dictionary the default values in the code will be used. So the programmer has the choice of using the Info.plist, setting the configuration values in code, or simply using the default values.
Since errors are easily made, it is recommended to check the values in the Info.plist file before assigning them to the configuration parameters. This can save hours of bug-hunting!
Btw: Please note that I have manually changed the above code to be more compact for display purposes on screen. It is no longer identical to the swifterLog code. But you can check that out yourself by downloading SwifterLog...
Using the Info.plist is a nice enough feature, but when you code-sign your application it is no longer possible to change the Info.plist after distribution. While the end-user can edit the Info.plist file, GateKeeper won't start the application if the Info.plist is modified. If you need the capability for the end-user to modify plist values, you are out of luck. Unless you distribute the application without code-signing. I.e not through the App-store. Which of course can only be done for OS-X applications, not iOS applications.
Happy coding
Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH
We don't get the world we wish for... we get the world we pay for.
But then it is necessary to access the Info.plist's properties during initialisation. Luckily Apple has made this easy. I use the following code to achieve this:
init() {
// Try to read the settings from the app's Info.plist
if let infoPlist = Bundle.main.infoDictionary,
let config = infoPlist["SwifterLog"] as? Dictionary<String, AnyObject> {
if let als = config["aslThreshold"] as? NSNumber {
if als.intValue >= Level.debug.rawValue &&
als.intValue <= Level.none.rawValue {
aslThreshold = Level(rawValue: als.intValue)!
} else {
// "Info.plist value for aslThreshold in SwifterLog out of bounds"
}
}
....
}The corresponding entries in the plist file look as follows:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
...
<key>SwifterLog</key>
<dict>
<key>aslThreshold</key>
<integer>8</integer>
<key>logfileThreshold</key>
<integer>8</integer>
<key>stdoutThreshold</key>
<integer>0</integer>
<key>logfileMaxSizeInBytes</key>
<integer>1000000</integer>
<key>logfileMaxNumberOfFiles</key>
<integer>20</integer>
<key>logfileDirectoryPath</key>
<string></string>
</dict>
</dict>
</plist>
Consequently, in the source code I first need to gain access to the Info.plist, then the SwifterLog dictionary and then to the configuration items.
Swift allows us to encapsulate this nicely by using the conditional assignment. Notice that this also ensures that when the Info.plist does not contain a SwifterLog dictionary the default values in the code will be used. So the programmer has the choice of using the Info.plist, setting the configuration values in code, or simply using the default values.
Since errors are easily made, it is recommended to check the values in the Info.plist file before assigning them to the configuration parameters. This can save hours of bug-hunting!
Btw: Please note that I have manually changed the above code to be more compact for display purposes on screen. It is no longer identical to the swifterLog code. But you can check that out yourself by downloading SwifterLog...
Using the Info.plist is a nice enough feature, but when you code-sign your application it is no longer possible to change the Info.plist after distribution. While the end-user can edit the Info.plist file, GateKeeper won't start the application if the Info.plist is modified. If you need the capability for the end-user to modify plist values, you are out of luck. Unless you distribute the application without code-signing. I.e not through the App-store. Which of course can only be done for OS-X applications, not iOS applications.
Happy coding
Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH
We don't get the world we wish for... we get the world we pay for.
2015-03-20
SwifterLog, a log utility for Swift applications
As you may have guessed from me writing about ASL recently, I have developed a small logging framework called SwifterLog.
We have made this available through Github: https://github.com/Swiftrien/SwifterLog
It can be used to log to three destinations:
I would recommend to use the ASL for shipping versions only, and keep in mind that the ASL config file filters out DEBUG and INFO level messages.
STDOUT should imo only be used within Xcode.
When writing to logfiles, the size of the logfiles, as well as the maximum number of logfiles is configurable. It will auto delete logfiles that are no longer needed.
Since I use this in my own apps, it will occasionally be updated to reflect my growing logging needs. Of course if you have a special request, just ask...
Happy coding...
Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH
We don't get the world we wish for... we get the world we pay for.
We have made this available through Github: https://github.com/Swiftrien/SwifterLog
It can be used to log to three destinations:
- The ASL facility (Apple's System Log)
- A set of logging files located either in Application Support or a directory of your choice
- Or simply to STDOUT, which is handy when working in Xcode, the output will directly appear in the Xcode console.
I would recommend to use the ASL for shipping versions only, and keep in mind that the ASL config file filters out DEBUG and INFO level messages.
STDOUT should imo only be used within Xcode.
When writing to logfiles, the size of the logfiles, as well as the maximum number of logfiles is configurable. It will auto delete logfiles that are no longer needed.
Since I use this in my own apps, it will occasionally be updated to reflect my growing logging needs. Of course if you have a special request, just ask...
Happy coding...
Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH
We don't get the world we wish for... we get the world we pay for.
Subscribe to:
Posts (Atom)