Create beautifully dynamic framed views in React Native beyond 9-Patch Images and capInsets

When working in more design-heavy projects like creative art apps and games, chances are, that ordinary frames and boxes don’t fit the bill and you need to go a step further. Next natural choice would probably be either using fixed-size images for an <ImageBackground> or working with a technique called capInsets (iOS) or 9-Patch images (android).

the left side shows the source image with PSD grid lines // the right side shows stretched results

When working with this technique, you take a regular image and add a little bit of whitespace around the actual image. Then you add thin black lines to the edges to define the repeatable areas (top and left line) and the full image size (right and bottom line). This way you create up to 9 areas that behave differently depending on the size of the container they are assigned to.

Image: UIImage — UIKit | Apple Developer Documentation

While those two solutions work more or less the same for both platforms (iOS has native support for this in react-native and there are solutions like React Native 9patch image or react-native-image-capinsets for android), you are stuck with certain limitations. The main limitation is that you can only stretch and not tile/repeat the side and middle sections. Furthermore, you are still very limited in regards to flexibility with background colour, positioning and for example, adding content elements that overlay the frame and even “break out” of the container.

When making games, I soon realised that those solutions wouldn’t meet my requirements. That’s why I started building a reusable box component as mentioned in my latest article and as this is (due to some react native limitations) not as straight-forward to use as I would like it to be, let’s go through a few examples on how to use the Box I made.

Keep in mind that I’m not a designer. I can see and appreciate beauty but as a programmer, my beauty lies in the code, not the UI. Therefore I’m either grabbing free to use stock from the internet or offer you the horrendous experience of programmers art.

Let’s start with a simple one. The corners will have the same dimensions as the sides and we’ll use a custom background for the content area.

Screenshot of two instances of my Box component

Both boxes were made with the same component and images for the edges and corners.

// home.jsx
import demoImage from '../../assets/demo'
/* some code */const setup2 = {
...demoImage,
width: 240,
height: 440,
margin: 10,
backgroundColor: 'green',
padding: {
top: 20,
right: 20,
bottom: 20,
left: 20,
},
}
/* more code */<Spacing setup={setup2}>
<
Text>
Spacing Demo
</Text>
</
Spacing>

When working with multiple background images, I usually slice the source image in Photoshop, name the files accordingly (using the same prefix and the cardinal directions [N, NE, NW, E, W, S, SE, SW]) and include an index file that exports my images and their dimensions (as seen below). That way I can easily spread the image object into my custom setup (as seen above) and won’t have to repeat the same data multiple times.

import demoN from './demo_frame_01_N.png'
import demoNE from './demo_frame_01_NE.png'
import demoNW from './demo_frame_01_NW.png'
import demoE from './demo_frame_01_E.png'
import demoW from './demo_frame_01_W.png'
import demoS from './demo_frame_01_S.png'
import demoSE from './demo_frame_01_SE.png'
import demoSW from './demo_frame_01_SW.png'

const image = {
N: {
source: demoN,
height: 37,
},
S: {
source: demoS,
height: 37,
},
E: {
source: demoE,
width: 35,
},
W: {
source: demoW,
width: 35,
},
NE: {
source: demoNE,
width: 35,
height: 37,
},
NW: {
source: demoNW,
width: 35,
height: 37,
},
SE: {
source: demoSE,
width: 35,
height: 37,
},
SW: {
source: demoSW,
width: 35,
height: 37,
},
}

export default image

We can now easily switch out our source data containing the images and sizes to change the design on the go.

I’ll add a few more examples later that show of more qualities like adding decorations to the container like ribbons, badges and buttons that reach outside of the container’s frame as well as a setup with extruding edges, using offset to display the background only within the frame’s constraints.

Until I’ve found the time to push this to the demo repo, here’s the current code from my initial testing phase.

// components/Box/index.jsx
import React from 'react'
import PropTypes from 'prop-types'
import {
View,
StyleSheet,
ImageBackground,
} from 'react-native'
import * as defaultFrame from '../../../assets/test'

const Spacing = ({ children, setup }) => {
const {
width,
height,
margin,
N = {},
S = {},
E = {},
W = {},
NE = {},
NW = {},
SE = {},
SW = {},
backgroundOffset = {},
backgroundColor,
offset = {},
padding = {},
scale = 1,
} = setup

const frame = {
image_N: N.source || defaultFrame.test_N,
image_S: S.source || defaultFrame.test_S,
image_E: E.source || defaultFrame.test_E,
image_W: W.source || defaultFrame.test_W,
image_NE: NE.source || defaultFrame.test_NE,
image_NW: NW.source || defaultFrame.test_NW,
image_SE: SE.source || defaultFrame.test_SE,
image_SW: SW.source || defaultFrame.test_SW,
}

const baseStyles = StyleSheet.create({
pos: {
position: 'absolute',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
},
demoContainer: {
backgroundColor: '#ff7100',
color: '#000',
borderRadius: 4,
borderWidth: 0.5,
borderColor: '#000',
},
})

const styles = StyleSheet.create({
boxOuter: {
width: width || 240,
height: height || 120,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
margin,
},
background: {
backgroundColor: backgroundColor || undefined,
position: 'absolute',
top: backgroundOffset.top || N.height * scale || 0,
bottom: backgroundOffset.bottom || S.height * scale || 0,
right: backgroundOffset.right || E.width * scale || 0,
left: backgroundOffset.left || W.width * scale || 0,
},
container: {
// ...baseStyles.demoContainer,
position: 'absolute',
paddingTop: padding.top || 10,
paddingRight: padding.right || 10,
paddingBottom: padding.bottom || 10,
paddingLeft: padding.left || 10,
top: offset.top || N.height || 50,
right: offset.right || E.width || 50,
bottom: offset.bottom || S.height || 50,
left: offset.left || W.width || 50,
},
pos_N: {
...baseStyles.pos,
left: (NW.width || 0) * scale,
right: (NE.width || 0) * scale,
height: N.height * scale,
alignSelf: 'center',
top: 0,
},
pos_S: {
...baseStyles.pos,
left: (SW.width || 0) * scale,
right: (SE.width || 0) * scale,
height: S.height * scale,
alignSelf: 'center',
bottom: 0,
},
pos_E: {
...baseStyles.pos,
width: E.width * scale,
top: (NE.height || 0) * scale,
bottom: (SE.height || 0) * scale,
right: 0,
},
pos_W: {
...baseStyles.pos,
width: W.width * scale,
top: (NW.height || 0) * scale,
bottom: (SW.height || 0) * scale,
left: 0,
},
pos_NE: {
...baseStyles.pos,
width: NE.width * scale,
height: NE.height * scale,
top: 0,
right: 0,
},
pos_SE: {
...baseStyles.pos,
width: SE.width * scale,
height: SE.height * scale,
bottom: 0,
right: 0,
},
pos_SW: {
...baseStyles.pos,
width: SW.width * scale,
height: SW.height * scale,
bottom: 0,
left: 0,
},
pos_NW: {
...baseStyles.pos,
width: NW.width * scale,
height: NW.height * scale,
top: 0,
left: 0,
},
})

return (
<
View style={styles.boxOuter}>
<
View style={styles.background} />
{
[
N, E, S, W, NE, NW, SE, SW].map((loc) => (
<
ImageBackground
source={frame[`image_${loc.id}`]}
key={loc.id}
style={styles[`pos_${loc.id}`]}
/>
))
}
<
View style={styles.container}>
{
children}
</
View>
</
View>
)
}

Spacing.defaultProps = {
children: undefined,
}

Spacing.propTypes = {
children: PropTypes.node,
setup: PropTypes.object.isRequired, // ToDo: wtf type number?
}

export default Spacing

Working with the Box in projects

When starting a new project or sometimes even for quick prototyping, I usually import my Box Component and create a set of components using the Box under the surface. That way I can create all the variations I need with one tool and use them wherever I need them in my code. That way, all the setup work with source images and positioning are centralised and I keep my actual template files cleaner, shorter and easier to read.

If you want to use the box or one of my other reusable components in your projects, I’m currently finalising the first release of my rn-gui framework and over the next couple of weeks, I’m gonna add more of my home brewed elements to the framework, one piece at a time, as I still need to add tests, documentation and such before I release them as production-ready to the public. I will set up a GitHub page providing an overview of planned and implemented features and solutions and if you are a patreon of mine, you can even vote on the priority of the planned components or add suggestions to the backlog.

Some words about me:

If you want to see more of my work and progress, feel free to check out my series on building a React Native Web App with Hooks, Bells, and Whistles with Expo SDK33. I’m also currently working on a new series about building more complex and scalable apps using React/Redux, where I’ll go into details about how and why I do stuff the way I do as well as some articles on my experience in building games for web and mobile with React.

And if you feel really supportive right now, you can always support me on patreon, thus allowing me to continue to write tutorials and offer support in the comments section.

I’m a Web / App Developer & father 👨‍👩‍👧 doing freelance and part-time agency work since 2003, 💻 building stuff on the side 🕹 and attending conferences 🎟