connect.tech- enhancing your workflow with xcode source editor extensions
Post on 12-Jan-2017
128 Views
Preview:
TRANSCRIPT
Enhancing Your Workflow with Xcode Source Editor Extensions
By: Jesse Black Software Engineer stable|kerneljesse.black@stablekernel.com
@stablekernel
Hi, I’m Jesse Black.
• Programming for over eight years • Created a Mac App for my family business • Worked for 3 years with Gramercy Consultants developing iOS and Android apps • Working for stable|kernel for the past 3 years developing iOS apps, Android apps
and their supporting APIs
We’re stable|kernel.
stable|kernel is an Atlanta-based mobile development company to craft smartly-designed mobile applications that connect brands directly with their users.
Enhancing Your Workflow with Xcode Source Editor Extensions
@stablekernel
Overview
Mac App Extensions Xcode Source Editor Extensions Demo: Create Source Editor Extension and debugging it XC Classes Example: JSON formatter Demo: JSON formatter in action
Mac App Extensions
@stablekernel
• Mac App Extensions
• Affect the user experience of other apps
• Need to be lightweight and run fast
• Require user interaction in order to be run
• Distribute via host app
Why use Xcode Source Editor Extensions
@stablekernel
• Less context switching
• It is too fun to customize your workflow
Xcode Source Editor Extensions
@stablekernel
• Differences with general Mac App Extensions
• Extensions are installed by just putting the host application in your applications folder
• Only works with one type of target application, which is Xcode
• Doesn’t have UI
Xcode Source Editor Extensions
@stablekernel
• Allows you to add custom commands under Xcode’s Editor menu
• Commands can manipulate text and text selections
• Users can put bind keyboard shortcuts to invoke your commands
Limitations to Xcode Source Editor Extensions
@stablekernel
• Can only start with user interaction
• Lack of ability to integrate into the build process and archive process. It is for modifying source code while it is being edited.
Expectations of Xcode Source Editor Extensions
@stablekernel
• Fast start up
• Fast execution
• Security and Stability
Demo
@stablekernel
Overview
Create Source Editor Extension How to install How to debug How to uninstall
Defining Commands
@stablekernel
• Command Identifier
• Class Name
• Command Name
Commands can be defined in the Extension’s plist or by supplying a dictionary at runtime. Commands provided at runtime override commands defined in the plist.
XC Source Editor Extension
@stablekernel
func extensionDidFinishLaunching() {}
var commandDefinitions: [[XCSourceEditorCommandDefinitionKey: Any]] {}
XC Source Editor Command
@stablekernel
func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void {
XC Source Editor Command
@stablekernel
• Modify text, text selection
• Exit early with an error message that is displayed in Xcode
XC Source Editor Command Invocation
@stablekernel
• Encapsulates all details needed to fulfill the user’s request • contentUTI • usesTabsForIndentation • indentationWidth • tabWidth • buffer
XC Source Text Buffer
@stablekernel
• completeBuffer • lines • selections
• defined in terms of lines and columns
Example: JSON formatter
@stablekernel
• Define minify and prettify commands • Implement XCSourceEditorCommand’s perform method
• Validate requirements for command • Get first text selection • Return error if selected text isn’t valid JSON • Use JSONSerialization class to format text • Replace selected text
XCSourceEditorExtension
@stablekernel
var commandDefinitions: [[XCSourceEditorCommandDefinitionKey: Any]] { return [ [ .classNameKey: "JeSON.SourceEditorCommand", .identifierKey: JeSONInvocation.Minify.rawValue, .nameKey: "Minify" ], [ .classNameKey: "JeSON.SourceEditorCommand", .identifierKey: JeSONInvocation.Prettify.rawValue, .nameKey: "Prettify" ], ] }
XCSourceEditorExtension
@stablekernel
enum JeSONInvocation: String { case Prettify = "com.jesseblack.HelloWorldProject.JeSON.Prettify" case Minify = "com.jesseblack.HelloWorldProject.JeSON.Minify" }
@stablekernel
func textAtSelectionIndex(_ at: Int, buffer: XCSourceTextBuffer) -> String { let textRange = buffer.selections[at] as! XCSourceTextRange let selectionLines = buffer.lines.enumerated().filter { (offset, element) -> Bool in return offset >= textRange.start.line && offset <= textRange.end.line }.map { return $1 } return selectionLines.enumerated().reduce("") { (result, enumeration) -> String in let line = enumeration.element as! String if enumeration.offset == 0 && enumeration.offset == selectionLines.count - 1 { let startIndex = line.index(line.startIndex, offsetBy: textRange.start.column) let endIndex = line.index(line.startIndex, offsetBy: textRange.end.column + 1)
return result + line.substring(with: startIndex..<endIndex) } else if enumeration.offset == 0 { let startIndex = line.index(line.startIndex, offsetBy: textRange.start.column) return result + line.substring(from: startIndex) } else if enumeration.offset == selectionLines.count - 1 { let endIndex = line.index(line.startIndex, offsetBy: textRange.end.column + 1) return result + line.substring(to: endIndex) } return result + line } }
@stablekernel
func replaceTextAtSelectionIndex(_ at: Int, replacementText: String, buffer: XCSourceTextBuffer) { let textRange = buffer.selections[at] as! XCSourceTextRange
let lastLine = buffer.lines[textRange.end.line] as! String let endIndex = lastLine.index(lastLine.startIndex, offsetBy: textRange.end.column+1) let suffix = lastLine.substring(from: endIndex) let firstLine = buffer.lines[textRange.start.line] as! String let startIndex = firstLine.index(firstLine.startIndex, offsetBy: textRange.start.column) let prefix = firstLine.substring(to: startIndex) let range = NSMakeRange(textRange.start.line, textRange.end.line - textRange.start.line + 1) buffer.lines.removeObjects(in: range) buffer.lines.insert(prefix+replacementText+suffix, at: textRange.start.line) let newRange = XCSourceTextRange(start: textRange.start, end: textRange.start) buffer.selections.setArray([newRange]) }
Performing the command
@stablekernel
func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void { guard let jeSONInvocation = JeSONInvocation(rawValue: invocation.commandIdentifier) else { let errorInfo = [NSLocalizedDescriptionKey: "Command not recognized"] completionHandler(NSError(domain: "", code: 0, userInfo: errorInfo)) return } let buffer = invocation.buffer guard buffer.selections.count == 1 else { let errorInfo = [NSLocalizedDescriptionKey: "Command only handles 1 selection at a time"] completionHandler(NSError(domain: "", code: 0, userInfo: errorInfo)) return }
Performing the command
@stablekernel
// perform continued
let selectedText = textAtSelectionIndex(0, buffer: buffer) let data = selectedText.data(using: .utf8) do { let jsonObject = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions()) switch jeSONInvocation { case .Minify: let miniData = try! JSONSerialization.data(withJSONObject: jsonObject, options: JSONSerialization.WritingOptions()) let string = String.init(data: miniData, encoding: .utf8)! replaceTextAtSelectionIndex(0, replacementText: string, buffer: buffer) case .Prettify: let miniData = try! JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted) let string = String.init(data: miniData, encoding: .utf8)! replaceTextAtSelectionIndex(0, replacementText: string, buffer: buffer) } } catch { completionHandler(error) }
completionHandler(nil) }
JSON Formatter Demo
@stablekernel
Overview
See all the hard work pay off
Questions?
Business Inquiries:Sarah WoodwardDirector of Business Developmentsarah.woodward@stablekernel.com
Jesse BlackSoftware Engineerjesse.black@stablekernel.comblog.stablekernel.com@JesseBlack82
http://www.slideshare.net/stablekernel https://github.com/JesseBlack82/HelloExtensions
top related