The fewer sources of truth we have for a design system, The more efficient we are. — Jon Gold

Designing systems with fewer sources of truth will be more efficient. Jon Gold, full stack designer

background

1. Building block tool chain system

Some time ago, we published the Building Block Sketch Plugin on the official account of Meituan Technical Team: Design the intimate partner “of the classmate, never thought of, this is only used in Meituan take-away C end of plug-in has received more attention and Meituan multiple business team have to throw out the” olive branch “to us, that want to access and and expressed his willing to joint development intention, other Internet peer have also asked related technologies, let some of our” overwhelmed “at the moment. When we think back to writing the first article, we are still a little uneasy inside. As a design tool for UI students, some RD students have not even heard of the name Sketch. We carefully modified every sentence of our last article to make the content more rich and interesting. At that time, we were worried that it would not be accepted by readers.

The “unexpected popularity” of the Building Block Sketch plug-in is indeed a little unexpected to us, but just like this, we know that UI consistency is a common problem faced by most development teams, and people have strong demands for implementing design standards, improving UI capabilities and improving production and research efficiency. In order to help more teams improve the efficiency of production and research, we set up the Kangaroo UI Co-construction project team, which manages and plans portal construction, tool chain construction and component construction in a unified way. The brand of tool chain is identified as “building block”, and the Building Block Sketch plug-in is an important part of it.

We set up a unified material market containing the same design elements. PM picked up components in the material market and produced prototypes through Axure plug-in. UI/UE produces the required design draft through the design specification in the material market based on the Sketch plug-in; The components in the material market correspond to the components in the RD code repository, thus forming a closed loop. In the future, we hope to provide better service experience for middle and background projects and non-dependent experience projects through high-fidelity prototype output, and give product students the ability to directly output prototypes to the technical side.

2. Building blocks plug-in platformization

With the establishment of the “Building Blocks” brand, more and more teams hope to access the Building Blocks Sketch plug-in, and some of them are also discussing the possibility of technical cooperation with us. The UI design language has a strong correlation with its own business. The color system, graphics, grid system, projection system and graphic relations of different businesses vary greatly, and the absence of any link will lead to the destruction of consistency. Businesses all hope to achieve the implementation of design specifications through building block plug-ins. In order to help more UI students in the team improve the design efficiency, save the time of page adjustment for RD students, and make the App interface consistent, so as to better convey the brand proposition and design concept, we decided to transform the building blocks plug-in into a platform. Platformization means that building blocks plug-in can access the whole set of design specifications of each business team. Through platformization transformation, the design elements provided by building blocks plug-in can be strongly associated with business and meet the design requirements of different business teams.

The building blocks plugin was originally just an attempt to improve UI/RD collaboration efficiency, with the initial goal of UI consistency only, but now it has become a medium to improve overall production and research efficiency, carrying more and more functions. The core mission of Building Blocks is to provide efficient ways to acquire design elements around the daily work of design and make the work easier. How to promote the implementation of design specifications, and output to various business systems for flexible use, is the answer we continue to pursue. We have been working hard to explore a more efficient mode of collaboration in r&d and design.

After a period of platform construction, 7 design teams of Meituan have been connected to the building blocks plug-in, covering most of the design students of Meituan Home Business Division. In the future, we will continue to promote the building blocks plug-in platform construction and constantly improve its functions, hoping to build the building blocks plug-in into a first-class brand in the industry.

3. Advanced development of Sketch plug-in

This first article is probably one of the few introductory tutorials, and this is probably the only advanced development article you’ll find. Advanced development mainly involves how to switch business party data, that is, after selecting the business party, the corresponding components, colors and other design materials are switched to the elements uploaded by the current business party in the material market. Convert the Library file hosting the component Library into a format that can be recognized by the plug-in, and display it on the plug-in for the designer to use when drawing the design draft. Some methods to optimize operation efficiency and improve user experience.

Due to the fact that the code of Sketch plug-in is strongly related to business and the implementation method is complex, there may be some sensitive information, so there is basically no mature open source plug-in. For some complex function in the development, we often “zhang two monks scratching their heads,” come on “” or the function idea also appear more than once, but every time the meeting see next to the design of the students in the use of” building blocks “plug-in drawing carefully, and strengthened our faith again and again, or try again to work overtime, maybe can do it? A “compromise” may lead to the slow collapse of the whole project, so we have been building blocks plug-in into the industry’s leading plug-in belief. If seen in the first article you already know how to develop a plugin, then through the study of this article and you will be able to achieve a strong business association and the mature tools that function can be customized, is not so much to introduce how to develop an advanced version of the Sketch plug-in, than to share with everybody for a commercial project experience.

Supports multi-service switching

In order not to feel awkward when faced with the soul question “Can we plug in blocks?”, the platforming process started quickly. The core of platformization is actually when the business line changes, the material in the material market is switched synchronously. Therefore, we need to carry out the following operations: First, establish global variables to store the business party information and authentication information described by the current user; After the user selects the function module, the corresponding material is pulled according to the business side described by the user. Processing Library and other materials and rendering page display; Change the related Layer in the artboard based on the material information. This part mainly introduces how to rely on persistent storage to achieve the function of business switch, just like in the first enlightenment document, here will not post a large section of code, will only help you sort out the most core process, I believe that after you practice once, the future difficulties can be easily solved.

1. Define generic variables

The material displayed by the function module is relevant to the currently selected business, so some global state variables need to be added to the Redux initialization state of each function module. For example, all modules need to use businessType to determine the currently selected business, use Theme to switch themes, use commonRequestParams to get user authentication information, and so on.

export const ACTION_TYPE = 'roo/colorData/index';
const intialState = {
  id: 'color'.title: 'Color library'.theme: 'black'.businessType: 'waimai-c'.commonRequestParams: {
    userInfo: ' ',}};export default reducerCreator(intialState, ACTION_TYPE);
Copy the code

2. Implement data exchange

Step 1: The WebView side obtains the user’s choice and passes the selected business side data to the plug-in side through window.postMessage method.

window.postMessage('onBusinessSelected', item);
Copy the code

Step 2: Plugin side receives data from WebView side through webviewcontents.on () method. In addition to storing JSON data, Layer, Document, and even Session variable are supported.

 webViewContents.on('onBusinessSelected'.item= > {
    Settings.setSettingForKey('onBusinessSelected'.JSON.stringify(item));
  });
​
// In addition, the plugin side can also inject data into the WebView through localStorage
browserWindow.webContents.executeJavaScript(
      `localStorage.setItem("${key}","${data}') `
);
Copy the code

Step 3: When the user selects a certain function module (such as “Illustration library”) through the toolbar, the click event monitoring of NSButton will be called back. At this time, in addition to WebView Show and Focus, the business information stored in the second step needs to be imported to load the material data of the current business side.

// The function module opened by the user
const button = NSButton.alloc().initWithFrame(rect) 
  button.setCOSJSTargetFunction(() = > {
    initWebviewData(browserWindow);
    browserWindow.focus();
    browserWindow.show();
  });
​
// Inject global initialization information
function initWebviewData(browserWindow) {
  const businessItem = Settings.settingForKey('onBusinessSelected');
  browserWindow.webContents.executeJavaScript(`initBusinessData(${businessItem}) `);
}
Copy the code

After receiving the initialization information, the WebView function module starts to prepare data for page rendering. The object.keys () method returns an array of the given Object’s own enumerable properties, traverses the array to retrieve all the injected initialization data, and then updates state via Redux’s store.dispatch method. At this point, the process of realizing the service switching function is all over. Do you feel very simple and want to try it yourself?

ReactDOM.render(<Provider store={store}><App /></Provider>.document.getElementById('root'));window.initBusinessData = data= > {
  const businessItem = {};
  Object.keys(data).forEach(key= > {
    businessItem[key] = { $set: initialData[key] };
  });
  store.dispatch(update(businessItem));
};
​
const update = payload= > ({
  type: ACTION_TYPE,
  payload,
});
Copy the code

3. Summary

Some people may ask, why WebView and Plugin side need data transfer, they are not part of the plug-in? The root cause is that our interface is presented through WebView, but the various operations on Layer are implemented through THE API of Sketch. WebView is just a web page and has nothing to do with Sketch itself, so bridge must be used to transfer data between the two. After the plug-in is started, it will pull the list of business parties from the server side. (2) Users select their own business side in WebView; (3) Business party data is transferred to Plugin side through Bridge and stored persistently through Settings API of Sketch, so that there is no need to select the business party again when Starting Sketch; ④ The user clicks the button in the plug-in toolbar to select the required function (such as palette library, component library, etc.), reads the current business side from the persistent data, and informs the WebView side to pull the current business side data. At this point, the whole process ends.

Automatic processing of Library files

This section shows you how to convert your Library files into a JSON format that your plug-in can recognize and display on your plug-in.

When asked what the most important feature of the Sketch plugin is, the component library is definitely the undisputed C bit. In the long iteration of the release, the new and old styles mix and become extremely difficult to maintain as features are added and THE UI is constantly reworked. By summarizing and sorting out the results of page walking, designers formulate design specifications, so as to select components with high reuse for component library construction. Through building component library can be standardized control, avoid random combination of controls, reduce page differences; Components in the component library meet business characteristics and have the cloud dynamic adjustment capability, which can be adjusted uniformly when specifications are updated.

Currently, we integrate components into Sketch for USE in UI into two general schools: One is based on the official Library file of Sketch. The designer generates the Library file (suffix. Sketch) by organizing the Symbol component with high reuse in business and uploading it to the cloud. The plug-in pulls the Library file into JSON and displays it in the operation panel for selection and use. The other is a framework like Airbnb’s React Sketchapp, which allows you to use React code to create and manage visual art and related design resources. Officially, it is called “Draw with code”. This solution is difficult to implement because design is essentially a combination of sensibility and rationality. Designers use Sketch to draw, rather than write with logic and hierarchy. It’s hard for them to understand the tree structure of the page, it’s expensive to get started, and it’s relatively expensive to maintain the code. We don’t judge which one is good or bad, just that the first one better meets our core needs.

1. Subscribe to the remote component library

The Library file is actually a document containing components, including Symbols, Text Styles, and Layer Styles. Storing the Library in the cloud makes it possible to share components between different documents and even between different teams. Because component libraries are up to date in real time, documents that use those components will be notified when their maintainers update the components in the library, ensuring that the draft always points to the latest design specification.

The way to subscribe to a cloud component library is simple: create a cloud component library, as described in the previous article. If you need to serve multiple design departments, create multiple libraries, each with a unique RSS address. After get the RSS address in plug-in, can Library. GetRemoteLibraryWithRSS method to subscribe.

// Add the remote component library when the plug-in is started
export const addRemoteLibrary = context= > {
  fetch(LibraryListURL, {
    method: 'POST'.headers: {
      'Content-Type': 'application/json',
    },
  })
    .then(res= > res.json())
    .then(response= > response.data)
    .then(json= > {
      const { remoteLibraryList } = json;
      _.forEach(remoteLibraryList, fileName= > {
        Library.getRemoteLibraryWithRSS(fileName, (err, library) = >{}); });return list;
    });
};
Copy the code

2. The Library file converts JSON data

The process of converting Sketch’s Library files to JSON is essentially the process of converting them to a format that WebView can recognize. Because building block plug-ins display components in certain groups in panels for designers to select, they need to organize their structure according to component categories. Sketch natively supports grouping them using the “/” Symbol: group-name/symbol-name. For example, the two Symbols named Button/Normal and Button/Pressed become part of the Button Group.

In practice, according to business needs, three or more groups can be named, through the split method to split the Symbol name through the “/” Symbol into an array, the first level of name, the second level of name and other names as different levels of JSON structure, the specific operation can refer to the following example code:

 const document = library.getDocument();
    const symbols = [];
    _.forEach(document.pages, page= > {
      _.forEach(page.layers, l= > {
        if (l.type && l.type === 'SymbolMaster') { symbols.push(l); }}); });// Group symbol and generate JSON data
 for (let i = 0; i < symbols.length; i++) {
      const name = symbols[i].name;
      const subNames = name.split('/');
      // Remove all Spaces
      const groupName = subNames[0].replace(/\s/g.' ');
      const typeName = subNames[1].replace(/\s/g.' ');
      const symbolName = subNames.join('/').replace(/\s/g.' ');
      result[groupName] = result[groupName] || {};
      result[groupName][typeName] = result[groupName][typeName] || [];
      result[groupName][typeName].push({
        symbolID:symbolID,
        name: symbolName,
      });
 }
Copy the code

After the above operations, a simplified VERSION of the JSON file looks like this:

{
    "Meituan Takeout C-side Component Library": {
        "icon": [{
            "symbolID": "E35D2CE8-4276-45A1-972D-E14A06B0CD23"."name": "28 / question mark"}, {"symbolID": "E57D2CE8-4276-45A1-962D-E14A06B0CD61"."name": "27 / flowers"}}}]Copy the code

3. Symbol thumbnail processing

By default, WebView does not support the direct display of Symbol for users to drag and drop. There are two solutions to solve this problem: (1) After dump analysis of the header file of Sketch, it is found that thumbnies can be exported using imageForSymbolAncestry method of MSSymbolPreviewGenerator, which supports setting of image size, color space and other attributes, with the advantage of flexibility. You can configure as much as you want, but at the risk of late API changes; (2) Directly use the export method provided by sketchDOM to export the Symbol component as a thumbnail, and then display the thumbnail in WebView. When dragging the thumbnail to the drawing board, replace it with the corresponding Symbol in the Library.

import sketchDOM from 'sketch/dom';
​
sketchDOM.export(symbolMaster, {
     overwriting: true.'use-id-for-name': true.output: path,
     scales: '1'.formats: 'png'.compression: 1});Copy the code

4. Summary

The above is a basic process to achieve platformization. I don’t know if you have heard the “fog” at this time. Here, let’s review the core point again. This section focuses on two things: first, how plug-ins can support multiple business parties, that is, by selecting the relevant business party in the business party list of plug-ins, you can switch the corresponding design resources; Second, how to process Library files and convert them to JSON for WebView presentation. The specific process is as follows:

  1. UI students from different design groups completed the Library containing various components and uploaded it to the cloud through the background.
  2. RD: According to the design team the current user belongs to, pull the corresponding design materials including Library, color, image, iconFont and other design materials can be directly displayed, but Library files do not support direct display in WebView, so it needs to be processed.
  3. According to the component naming rules agreed with UI, the first-level name, second-level name and other names are divided by “/” as different levels of JSON structure, and the Symbol is converted into PNG format thumbnail by the export method provided by sketchDOM to be displayed in the plug-in.
  4. Drag the selected thumbnail onto the Sketch palette and replace the thumbnail with the real Symbol in the Library.

Optimization of operating experience

Once you have completed the above steps, you are ready to have a plug-in that supports multiple business parties. However, as more and more business parties have access to the Building Block Sketch plug-in, we often hear jokes about the plug-in in addition to the praise that can significantly improve efficiency. There are many mature plug-ins on the market. We can’t limit other people’s choices, so we can only make building blocks more usable. This section mainly introduces the optimization methods of plug-ins. In order to better listen to people’s opinions, the building blocks plug-in through various measures to learn what users really think. First blocks plug-in Meituan internal access to the TT (Trouble Tracker) system, compared to a lot of professional system, TT without any professional process and customization, do only pure flow, is a set of suitable for inside the company, general questions, responses, and tracking system, the problem of user feedback automatically create a work order and associated with corresponding RD, Bugs can be fixed most quickly; Add feedback channels inside the plug-in, and the user feedback will be sent to the relevant PM in time as the weight index of the next function schedule; Add multi-dimensional buried point statistics inside the plug-in to understand the core demands of UI students from two aspects of design penetration to high-frequency use. Here are some of the solutions to the high priority problems that were collated based on the feedback.

1. Optimized the operation interface

A lot of RD in the development process, the interface beautification is often sniffy, “this function can be used” is often hung in the mouth. Are the UI requirements really unappealing? A product designer once said that the earliest products could stand out from their competitors only by their functions, and whether they could be used became the standard of whether a product was qualified. Later, in the increasingly mature Internet environment, ease of use became a new and more important standard, and the functions of similar products became too close to be significantly different by constantly stacking features. And when similar products tend to be similar to the ease of use, how to solve the problem of product competitiveness is again in front of, then it is necessary to give product emotion, good product focus on function, excellent product focus on emotion, can let users feel warm in use.

The WebView optimization

When we passed the functional verification of carefully, high-spirited give user experience when the first edition blocks plug-in, was delighted to welcome the compliment, but instead got a lot of interaction experience bad feedback, “functionality alone passed” the theory in the eyes of a pixel is not miss designer sure it won’t work. The native WebView experience is often not good enough. In fact, it can be solved with some very simple Settings, but this does not mean that it is not important.

It is forbidden to drag the touchpad to cause the overall “rubber band” effect of the page, to prohibit user-select, overflow hiding and other operations, so that the WebView has a “native” effect, all of which will improve the actual user experience.

html.body.#root {
  height: 100%;
  overflow: hidden;
  user-select: none;
  background: transparent;
  -webkit-user-select: none;
}
Copy the code

In addition, in a formal environment (NODE_ENV is production), we do not want the current interface to respond to the right-click menu, and we need to mask the related event handling methods by adding an EventListener to the document.

document.addEventListener('contextmenu'.e= > {
  if (process.env.NODE_ENV === 'production') { e.preventDefault(); }});Copy the code

Toolbar optimization

Sketch is to designers what a code editor is to programmers. After the building block Sketch plug-in went out of Meituan and was adopted by more and more design teams, UI decided to make a new visual upgrade to the toolbar in order to make it more pleasing to the eye. Native interface development refers to user interface development through macOS AppKit. In plug-in development, some UI modules that need to be embedded in Sketch panel need native interface development, such as adsorption toolbar, which is developed through macOS native API.

Native development can be done in Objective-C or by writing JavaScript using CocoaScript. CocoaScript maps JS to Objective-C via Mocha, allowing us to call the internal API of Sketch and the Framework of macOS via JS. Here are some basics to know before developing nativelythrough CocoaScript:

  1. This is done through the framework() method before using the relevant framework, while Foundation and CoreGraphics are built in by default and do not need to be done separately.
  2. Some Objective-C selectors require pointer parameters, and since JavaScript does not support passing objects by reference, CocoaScript provides MOPointer as a proxy object for variable references.

UI adjustment is generally divided into three parts: layout adjustment, dynamic adjustment, image replacement. Each of these will be described in the following sections.

Layout adjustment

The UI requirement here is that the width of the NSButton fills the entire NSStackView, and the height is custom. Since this feature looks too simple, I thought that 0.5 days was more than enough, but I didn’t expect to get into 1 working day plus 2 weekends, because no matter how you set NSStackView neutron View size, it won’t work.

After resisting the “public opinion pressure” from people around me that “UI problems do not affect the use of functions, let’s optimize later when we have time”, I finally found a clue in the official documents: NSStackView A stack view employs an Auto Layout (the system’s constraint-based Layout feature) to arrange and align an array of views according to your specification. To use a stack view effectively, You need to understand the basics of Auto Layout constraints as described in Auto Layout Guide. “In short, NSStackView uses constraints for automatic layout (analogous to ConstraintLayout in Android), and when sizing, Anchor points need to be added, so sizing should be done via Anchor.

// Create a toolbar
const toolbar = NSStackView.alloc().initWithFrame(NSMakeRect(0.0.45.400));
toolbar.setSpacing(7);
/ / create NSButton
const button = NSButton.alloc().initWithFrame(rect)
// Set the width and height of NSButton
button
    .widthAnchor()
    .constraintEqualToConstant(rect.size.width)
    .setActive(1);
button
    .heightAnchor()
    .constraintEqualToConstant(rect.size.height)
    .setActive(1);
button.setBordered(false);
// Set the callback click event
button.setCOSJSTargetFunction(onClickListener);
button.setAction('onClickListener:');
// Add NSButton to NSStackView
toolbar.addView_inGravity(button, inGravityType);
Copy the code

Dynamic effect to adjust

NSButton has about 15 built-in click effects, which can be set through NSBezelStyle. The building blocks toolbar does not use the general processing method of inverted icon color after clicking, but sets the background color to light gray after clicking. If you want to customize some click effects, just set them in the callback of the NSButton click event.

onClickListener:sender= > {
  const threadDictionary = NSThread.mainThread().threadDictionary();
  const currentButton = threadDictionary[identifier];
   if (currentButton.state() === NSOnState) {
      currentButton.setBackgroundColor(NSColor.colorWithHex('#E3E3E3'));
   } else{ currentButton.setBackgroundColor(NSColor.windowBackgroundColor()); }}Copy the code

Image to load

The Sketch plugin supports loading both local and web images. When loading the local image, we can use the context.plugin method to get an MSPluginBundle object, which is the current plug-in bundle file. Its URL () method returns the path information of the current plug-in, which helps us find the local file stored in the plug-in. Loading web images is even easier. Nsurl.urlwithstring () gets an NSURL object initialized with the image url. Note that for web images use the HTTPS domain name.

// Local image loading
const localImageUrl = 
       context.plugin.url()
      .URLByAppendingPathComponent('Contents')
      .URLByAppendingPathComponent('Resources')
      .URLByAppendingPathComponent(`${imageurl}.png`);
​
// Network image loading
const remoteImageUrl = NSURL.URLWithString(imageUrl);
​
// Get the NSImage object according to ImageUrl
const nsImage = NSImage.alloc().initWithContentsOfURL(imageURL);
nsImage.setSize(size);
nsImage.setScalesWhenResized(true);
Copy the code

2. Implement efficiency optimization

Only in the design draft as much as possible to use components for design, and the content of the existing page through the designer’s walk comb gradually replaced into components, can really build component library to improve efficiency. As the design team gradually deposits the design language into the design specification and quantifies it into the building blocks plug-in, the number of components is increasing. As the building blocks plug-in component library is the most frequently used function by UI students, special attention should be paid to its operation efficiency.

The pre-component library is loaded

Preloading the component library logic to subscribe to the remote component library when the document is opened. The Action API provided by Sketch allows the plug-in to react to events in the application. To listen for callbacks, you only need to add a handler to the plug-in manifest.json file. This tells the plugin to execute the addRemoteLibrary function when a new document is opened.

{
      "script": "./libraryProcessor.js"."identifier": "libraryProcessor"."handlers": {
        "actions": {
          "OpenDocument": "addRemoteLibrary"}}}Copy the code

Add cache logic

The processing of the component Library requires converting the Library file into a JSON file with hierarchical information and exporting the Symbol to be displayed as a thumbnail. Because this step is time-consuming, you can cache the processed Library information and record the cached version of the Library through persistence. If the cached version is the same as the latest version and the thumbnails and JSON files are complete, you can directly use the cached information to greatly improve the loading speed of the Library. The following partial code is only an example:

verifyLibraryCache(businessType, libraryVersion) {
    const temp = Settings.settingForKey('libraryJsonCache');
    const libraryJsonCache = temp ? JSON.parse(temp) : null;
​
    // 1. Verify the cached version
    if(libraryJsonCache.version.businessType ! == libraryVersion) {return null;
    }
​
    // 2. Verify thumbnail integrity
    const home = getAssertURL(this.mContext, 'libraryImage');
    const path = join(home, businessType);
    if(! fs.existsSync(path) || ! fs.readdirSync(path)) {return null;
    }
​
    // 3. Verify the integrity of the service library Json file
    if (libraryJsonCache[businessType]) {
      console.info(` current${businessType}Hit cache ');
      return libraryJsonCache;
    } else {
      return null; }}}Copy the code

3. Customize the Inspector property panel

Mixed development with Objective-C engineering

With the continuous improvement of component library construction of each design group, the number of extracted components keeps increasing. Many UI students feedback that the modification panel of native component style of Sketch is not convenient enough to restrict the selection range. They hope to provide a more effective modification method of component overrides. In addition, when modifying “picture”, “icon”, “text” and other layers, you can make linkage selection with these functional modules of the building blocks plug-in. Implementing a custom Inspector panel makes it easier to operate and allows you to restrict changes.

The basic idea behind the custom properties panel feature is that when a component is dragged from the component library to the Sketch palette, the modifiable properties of the component can be displayed on the Properties panel of Sketch itself. We introduced objective-C native development to implement modifications to the Sketch interface. Why use native development? Although the official provides JS API and promises continuous maintenance, the work is always in Doing state. In addition, the official document is updated slowly and there is no clear time node. Therefore, it is convenient to use Native development for self-defining Native Inspector Panel, which requires Hook API. It’s also much friendlier to iOS developers, without having to learn about front-end interface development.

Xcode project configuration

The Xcode project builds a custom property panel to eventually generate a Framework that can be called by the JS side. You can create an Xcode project as described in the previous article, which automatically generates the test Sketch plug-in after each build and places it in the appropriate folder. It’s important to note that the plug-ins generated here are for development and debugging purposes only, and we’ll see how to integrate the Framework built by the XCode project into the JS main project.

The main function of the building block plug-in is implemented in JS code, but the custom property selection panel is implemented in Objective-C code. In order to realize the communication and bridge between the JS side function modules of building blocks plug-in and the OC side module, Mocha framework is used to realize relevant functions. Mocha framework is also used by Sketch official, which encapsulates the methods of the original side as official API and exposes them to JS side.

When the component is selected, the Sketch software will call back the onSelectionChanged method to the JS side. The JS side can use the Mocha framework to call the OC side and pass the parameters as OC objects. The Context passed from the JS side to the OC side is rich in content, including information about the selected component, related layers and Sketch itself. Although Sketch does not provide API, The Objective-C language itself has the ability of KVO to listen for object attributes. We can obtain the required object data by reading corresponding attribute values.

+ (instancetype)onSelectionChanged:(id)context {
 
    [self setSharedCommand:[context valueForKeyPath:@"command"]]; 
   
    NSString *key = [NSString stringWithFormat:@"%@-RooSketchPluginNativeUI", [document description]];
    __block RooSketchPluginNativeUI *instance = [[Mocha sharedRuntime] valueForKey:key];
​
    NSArray *selection = [context valueForKeyPath:@"actionContext.document.selectedLayers"];
    [instance onSelectionChange:selection];
    return instance;
}
Copy the code

The Sketch the official did not amend the properties of the panel ability exposed to the plug-in side, by querying the Sketch header files found through reloadWithViewControllers: refresh methods can achieve properties panel, but in the actual development process, has some version of the Sketch on the panel flashing problem, Here with the help of Method of Objective – C Swizzle features, directly modify reloadWithViewControllers: the runtime behavior of the solution.

[NSClassFromString(@"MSInspectorStackView") swizzleMethod:@selector(reloadWithViewControllers:)                                        withMethod:@selector(roo_reloadWithViewControllers:)                                                        error:nil];
Copy the code

The Swizzle method modifies the behavior of the original method, and in practice the post-Swizzle method should only be triggered if certain conditions are met.

Component property modification and replacement principles

The overwritable item (namely override) of a component can be modified through the custom panel. Currently, there are three affectedLayer that can be applied to overwritable item: Text, Image and Symbol Instance. The designer and the developer agreed on the layer format earlier to ensure that we could read and replace the attribute values of the layer in a uniform way.

Replace text

Based on class-dump, we can find the properties and methods of all classes declared in Sketch. The text processing strategy is to find all MSAvailableOverride objects in the layer. These objects represent the available override items. The modification of the text information is actually done by modifying the overridePoint of the MSAvailableOverride object.

id overridePoint = [availableOverride valueForKeyPath:@"overridePoint"];
[symbolInstance setValue:text forOverridePoint:overridePoint];
Copy the code

Change the style

The strategy for style setting is to find components in the Library that correspond to the currently selected component in a related style. Because all components follow the same naming format, the required components can be selected based on the component name.

Library = [self getLibraryBySymbol:layer]; library = [self getLibraryBySymbol:layer]; NSString *layerName = [symbol valueForKeyPath:@"name"]; // Predicate NSPredicate * Predicate = [NSPredicate predicateWithFormat:@"name BEGINSWITH [CD] %@", prefix]; / / select all components in line with the Predicate NSArray * filterResult = [allSybmols filteredArrayUsingPredicate: Predicate];Copy the code

When the user selects a style, the plug-in replaces the component on the draft with the selected one, using the changeInstanceToSymbol method in MSSymbolInstance. Note that changeInstanceToSymbol replaces only the components on the layer, but does not change the properties of the components on the layer. Information such as position and size needs to be handled separately.

// Before updating the components on the layer, We need to put components imported into the current document object id foreignSymbol = [libraryController importShareableObjectReference: sharedObject intoDocument:documentData]; / / update the layer components on [symbolInstance changeInstanceToSymbol: localSymbol];Copy the code

Debugging technique

The biggest problem with OC side development is that there is no official API support. Therefore, the debugger is very important, and the single step debugging allows us to very easily get inside Sketch to understand the data structure inside Document. The debug environment needs to be configured, but it is simple enough, and the improvement in development efficiency is exponential.

1. Configure the construction Scheme.

2.Attach to Sketch software so that breakpoint debugging can be achieved.

Mix and compile with the current JS project

1. Use the built-in @skpm/ XcodeProj-Loader in SKPM to compile the XCode project and copy the generated framework to the plug-in folder.

const framework = require('.. /.. /RooSketchPluginXCodeProject/RooSketchPluginXCodeProject.xcworkspace/contents.xcworkspacedata');Copy the code

2. Use the loadFrameworkWithName_inDirectory method provided by Mocha to set the name and path of the Framework to load.

function() {
    var mocha = Mocha.sharedRuntime();
    var frameworkName = 'RooSketchPluginXCodeProject';
    var directory = frameworkPath;
​
    if (mocha.valueForKey(frameworkName)) {
      console.info('JSloadFramework: `' + frameworkName + '` has loaded.');
      return true;
    } else if (mocha.loadFrameworkWithName_inDirectory(frameworkName, directory)) {
      console.info('JSloadFramework: `' + frameworkName + '` success! ');
      mocha.setValue_forKey_(true, frameworkName);
      return true;
    } else {
      console.error('JSloadFramework load failed');
      return false; }}Copy the code

3. Invoke methods in the framework.

Const frameworkClass = NSClassFromString('RooSketchPluginNativeUI'); / / call the method that exposed frameworkClass. OnSelectionChanged (context).Copy the code

Building blocks together

At present, building blocks plug-ins have been widely used in Meituan Home Business Division. We hope that building blocks brand products can be applied in a wider range in the future to help more teams implement design specifications, improve production and research efficiency, and welcome more teams to access the building blocks tool chain. As the first enlightening article said, we hope not only to produce first-class products, but also hope that building blocks plug-in can give everyone a break from busy work. We will continue to rely on the design language, building block tool chain as the starting point, constantly improve and optimize, expand the use of plug-in scenarios, make design and development easier.

People are always asking, is the block plugin working now? Not good enough, I mean. However, every time we review the requirements, we see the designers next to us earnestly using our plug-in to draw pictures, and see the fans of building blocks plug-in to make emojis for us to help us promote. We know that only by delivering the best products can we live up to everyone’s expectations.

The requirements of the second phase of platformization have just been determined, and the manpower allocation schedule has just been completed. We have thought of a large number of functions that will make you clap your hands and will soon embark on a new journey. Late at night, looking out of the window, people’s lights, one by one, the night sky is becoming more and more bright. Our goal is the sea of stars.

Thank you

Thanks to Xiaofei, Yanping, Yaoge, Yunpeng and Bingbing from delivery technology department for their strong support to the project. Thanks to the excellent designers ran Ran, Yu Han, Miao Lin, Xue Mei, Yuan Yuan and Jing Qi of Home Business Department. Thanks to Zhang Qi from flash purchase technical team, Yi Ting from CRM team and Wang Peng from CI for assisting in technical development.

reference

  • Summary of Baidu Sketch plug-in development
  • Iqiyi product workflow optimization: build component library to achieve high ROI
  • Ali focuses on Fusion, the middle and background UI solution of open source
  • Painting with Code
  • Sketch Developers Discussion

Recruitment information

Meituan takeout is looking for senior/senior engineers and technical experts on Android, iOS and FE. Welcome to join the family of takeout apps. If you are interested, please send your resume to [email protected] (please mark the subject of your email: Meituan takeout front).

For more technical articles, please follow the official wechat account of Meituantech.