Skip to content

Commit

Permalink
Merge pull request #3 from velos/feature/spm-documentation-updates
Browse files Browse the repository at this point in the history
SPM Tweaks and Documentation Updates
  • Loading branch information
zac committed May 14, 2021
2 parents 484a5f9 + 57701fb commit 79eb19b
Show file tree
Hide file tree
Showing 12 changed files with 142 additions and 154 deletions.
8 changes: 4 additions & 4 deletions Example/IconHarness.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,13 @@
inputFileListPaths = (
);
inputPaths = (
"$(PROJECT_DIR)/IconHarness/Icon.swift",
"$(PROJECT_DIR)/$(PRODUCT_NAME)/Icon.swift",
);
name = "Generate Icon";
outputFileListPaths = (
);
outputPaths = (
"$(PROJECT_DIR)/IconHarness/Assets.xcassets",
"$(PROJECT_DIR)/$(PRODUCT_NAME)/Assets.xcassets",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
Expand Down Expand Up @@ -341,7 +341,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.zacwhite.IconHarness;
PRODUCT_BUNDLE_IDENTIFIER = com.velosmobile.IconHarness;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
SWIFT_VERSION = 5.0;
Expand All @@ -363,7 +363,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.zacwhite.IconHarness;
PRODUCT_BUNDLE_IDENTIFIER = com.velosmobile.IconHarness;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
SWIFT_VERSION = 5.0;
Expand Down

This file was deleted.

81 changes: 81 additions & 0 deletions Example/IconHarness/Icon.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// Icon.swift
//
// Created by Zac White.
// Copyright © 2020 Velos Mobile LLC / https://velosmobile.com / All rights reserved.
//

import SwiftUI
import SwiftUIcon

struct Icon: View {

var body: some View {
/// Note: All of these assume a canvas size of 1024.
let spacing: CGFloat = 80
let radius: CGFloat = 135
let pillLength: CGFloat = 350
let pillRotation: Angle = .degrees(30)
let circleOffsetX: CGFloat = 50
let circleOffsetY: CGFloat = 20

let velosBackground = Color(red: 0/256, green: 180/256, blue: 185/256)
let velosPrimary = Color.white
let velosSecondary = Color(red: 248/256, green: 208/256, blue: 55/256)

return IconStack { canvas in
velosBackground
.edgesIgnoringSafeArea(.all)

HStack(alignment: .center, spacing: canvas[spacing]) {
HStack(alignment: .top, spacing: canvas[spacing]) {
Circle()
.fill(velosPrimary)
.frame(width: canvas[radius], height: canvas[radius])
.offset(x: canvas[circleOffsetX], y: canvas[circleOffsetY])
RoundedRectangle(cornerRadius: canvas[radius])
.fill(velosPrimary)
.frame(width: canvas[radius], height: canvas[pillLength])
.rotationEffect(pillRotation)
}
HStack(alignment: .bottom, spacing: canvas[spacing]) {
RoundedRectangle(cornerRadius: canvas[radius])
.fill(velosSecondary)
.frame(width: canvas[radius], height: canvas[pillLength])
.rotationEffect(pillRotation)
RoundedRectangle(cornerRadius: canvas[radius])
.fill(velosSecondary)
.frame(width: canvas[radius], height: canvas[pillLength])
.rotationEffect(pillRotation)
Circle()
.fill(velosSecondary)
.frame(width: canvas[radius], height: canvas[radius])
.offset(x: -canvas[circleOffsetX], y: -canvas[circleOffsetY])
}
}
}
}
}

#if DEBUG
struct Icon_Previews : PreviewProvider {
static var previews: some View {
Group {
Icon()
.previewIcon()
.previewLayout(.sizeThatFits)

Icon()
.previewHomescreen()
.background(
LinearGradient(
gradient: Gradient(colors: [.purple, .orange]),
startPoint: .bottom,
endPoint: .top
)
)
.previewLayout(.fixed(width: 500, height: 500))
}
}
}
#endif
41 changes: 0 additions & 41 deletions Icon/generate.swift

This file was deleted.

17 changes: 9 additions & 8 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "SwiftUIcon",
products: [
.library(
name: "SwiftUIcon",
targets: ["SwiftUIcon"]),
],
dependencies: [
.library(name: "SwiftUIcon", targets: ["SwiftUIcon"]),
],
dependencies: [],
targets: [
.target(
name: "SwiftUIcon",
dependencies: [],
path: "Icon"),
exclude: [
"main.swift",
"IconGenerator.swift"
],
sources: ["Icon+PreviewHelpers.swift"]
),
]
)
44 changes: 32 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,55 @@
# SwiftUIcon

**SwiftUIIcon** is a set of scripts and helpers for creating your iOS or iPadOS app icon in SwiftUI using shape and path drawing primitives.
**SwiftUIIcon** is a library that provides scripts and preview helpers for drawing and generating your iOS, iPad OS, and macOS app icons in SwiftUI using shape and path drawing primitives.

By defining your app's icon in code, SwiftUIcon can generate all image sizes you need, show you real-time previews of your icon changes and can even allow you to integrate your icon right into your app's view hierarchy.

<p align="center">
<img src="images/live-preview.gif">
<img src="https://user-images.githubusercontent.com/2525/118079196-9904ca80-b36c-11eb-8b76-b402b1acbeb0.gif">
</p>

## Getting Started

1. Add SwiftUIcon using Swift Package Manager
2. Add a Run Script build phase before your Copy Resources phase and specify the path to `Icon.swift` as the Input File (probably `$(PROJECT_DIR)/$(PRODUCT_NAME)/Icon/Icon.swift`) and the `Assets.xcassets` as the output file (probably `$(PROJECT_DIR)/$(PRODUCT_NAME)/Assets.xcassets`):
1. Add SwiftUIcon to your project using Swift Package Manager. Add the library to the app target.
2. Create an `Icon.swift` file in your project. It must have a `View` called `Icon`. Check out [the example](Example/IconHarness/Icon.swift) for a more complex icon, but this could help get you started:

```Swift
struct Icon: View {
var body: some View {
IconStack { canvas in
Color.blue
}
}
}

#if DEBUG
struct Icon_Previews : PreviewProvider {
static var previews: some View {
IconPreviews(icon: Icon())
}
}
#endif
```

3. Add a Run Script build phase before your Copy Resources phase calling the `build-script.sh` included in the package. You'll need to specify the path to your `Icon.swift` as the Input File (probably `$(PROJECT_DIR)/$(PRODUCT_NAME)/Icon.swift`) and the `Assets.xcassets` as the output file (probably `$(PROJECT_DIR)/$(PRODUCT_NAME)/Assets.xcassets`):

```bash
cd "${BUILD_ROOT}"/../../SourcePackages/checkouts/SwiftUIcon
chmod +x build-script.sh
./build-script.sh
"${BUILD_ROOT%Build/*}SourcePackages/checkouts/SwiftUIcon/build-script.sh"
```
<p align="center">
<img src="images/build-phase.jpg">
<img src="https://user-images.githubusercontent.com/2525/118078783-b71dfb00-b36b-11eb-8607-3024145ad097.jpg">
</p>

## Adding Your Icon

You can now edit the contents of the `IconStack` wrapper helper view inside of `Icon.swift`. This is essentially a `ZStack` with a `.center` alignment that provides a proxy to relatively position elements based on an assumed 1024x1024 canvas size. See [Apple's SwiftUI Drawing Tutorial](https://developer.apple.com/tutorials/swiftui/drawing-paths-and-shapes) for more info on drawing using SwiftUI.
You can now edit the contents of the `IconStack` wrapper helper view inside of `Icon.swift` and add any shapes, paths or colors you want to build your icon. See [Apple's SwiftUI Drawing Tutorial](https://developer.apple.com/tutorials/swiftui/drawing-paths-and-shapes) for more info on drawing using SwiftUI.

The `IconStack` is essentially a `ZStack` with a `.center` alignment that provides a `CanvasProxy` value to relatively position elements based on an assumed 1024x1024 canvas size. This is helpful because the `Icon` is rendered individually at all the required icon sizes, so anything that has a fixed size will not scale properly. Any place where you would normally have a hard-coded number like `42`, you should instead use `canvas[42]`. You can also scale fonts and other elements manually using the `CanvasProxy.scale` property. As an example, `Text("Testing").font(Font.system(size: 200 * canvas.scale))` should get you a properly scaled `Text` element.

## Limitations

* It's a bit of a hack, but [Velos](https://velosmobile.com/) is currently using it in a project 👍
* Installing this is pretty manual. Some currently technical issues prevent making it a Swift Package, but feel free to file an issue if you find a better way!
* Because the View is rendered at all the individual sizes, anything that has a fixed size will not scale properly. To get around this, you should use the `CanvasProxy` passed into the function builder of `IconStack`. Any place where you would normally have a hard-coded `value`, you should use `canvas[value]`. You can also scale fonts and other elements manually with the `CanvasProxy.scale` property. As an example, `Text("Testing").font(Font.system(size: 200 * canvas.scale))` should get you a properly scaled `Text` element.
* Since the run script essentially concatenates all the Swift files and runs it as a macOS script, any elements you use in your Icon must look the same on both iOS and macOS. So you should probably stay away from actual UI controls 😉
* Since the run script essentially concatenates all the Swift files and runs it as a macOS script, any elements you use in your Icon will be rendered using your Mac's version of SwiftUI. Because of this, there might be some differences or changes between macOS versions or between macOS and iOS that could manifest in your Icon. You should probably also stay away from putting UI elements like `Slider` in your Icon too 😉

## License
MIT
Expand Down
File renamed without changes.
File renamed without changes.
17 changes: 11 additions & 6 deletions IconGenerator/main.swift → Sources/SwiftUIcon/main.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
//
// main.swift
//
// Created by Zac White.
// Copyright © 2020 Velos Mobile LLC / https://velosmobile.com / All rights reserved.
//

import Foundation

let env = ProcessInfo.processInfo.environment

guard
let assets = env["SCRIPT_OUTPUT_FILE_0"],
let deviceFamily = env["TARGETED_DEVICE_FAMILY"],
let projectPath = env["PROJECT_DIR"],
let project = env["PROJECT"]
else {
guard let assets = env["SCRIPT_OUTPUT_FILE_0"],
let deviceFamily = env["TARGETED_DEVICE_FAMILY"],
let projectPath = env["PROJECT_DIR"],
let project = env["PROJECT"] else {
print("error: Missing environment variables, this should have been caught by the build-script.sh")
exit(1)
}
Expand Down
10 changes: 5 additions & 5 deletions build-script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@ TMPFILE=`mktemp /tmp/SwiftUIcon.swift.XXXXXX` || exit 1
trap "rm -f $TMPFILE" EXIT


[[ -s "$SCRIPT_INPUT_FILE_0" ]] || {
[[ -s "$SCRIPT_INPUT_FILE_0" ]] && [ "${SCRIPT_INPUT_FILE_0: -5}" == "swift" ] || {
echo "error: You must specify your Icon.swift as the first Input File in the Build Phase."
exit 1
}

[[ -s "$SCRIPT_OUTPUT_FILE_0" ]] || {
[[ -s "$SCRIPT_OUTPUT_FILE_0" ]] && [ "${SCRIPT_OUTPUT_FILE_0: -8}" == "xcassets" ] || {
echo "error: You must specify your Assets file as the first Output File in the Build Phase."
exit 1
}

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"

HELPER=$SCRIPT_DIR/Icon/Icon+PreviewHelpers.swift
GENERATOR=$SCRIPT_DIR/IconGenerator/IconGenerator.swift
MAIN=$SCRIPT_DIR/IconGenerator/main.swift
HELPER="$SCRIPT_DIR/Sources/SwiftUIcon/Icon+PreviewHelpers.swift"
GENERATOR="$SCRIPT_DIR/Sources/SwiftUIcon/IconGenerator.swift"
MAIN="$SCRIPT_DIR/Sources/SwiftUIcon/main.swift"

# Concatenate all files and remove import that is most likely in the input file
cat $SCRIPT_INPUT_FILE_0 $HELPER $GENERATOR $MAIN | grep -v "import\sSwiftUIcon" > $TMPFILE
Expand Down
Binary file removed images/build-phase.jpg
Binary file not shown.
Binary file removed images/live-preview.gif
Binary file not shown.

0 comments on commit 79eb19b

Please sign in to comment.