From d5e80fa5c1dbacdcc43a8c31436d823aca305285 Mon Sep 17 00:00:00 2001 From: Justin Espedal Date: Sat, 2 Nov 2019 17:30:06 +0900 Subject: [PATCH] Allow use of launch storyboard instead of launch images on iOS --- src/lime/tools/HXProject.hx | 15 ++ src/lime/tools/ImageHelper.hx | 25 +++ src/lime/tools/LaunchStoryboard.hx | 66 +++++++ src/lime/tools/ProjectXMLParser.hx | 80 +++++++++ templates/ios/storyboards/splash.storyboard | 48 +++++ .../{{app.file}}.xcodeproj/project.pbxproj | 8 +- .../{{app.file}}/{{app.file}}-Info.plist | 2 + tools/platforms/IOSPlatform.hx | 167 ++++++++++++++---- 8 files changed, 373 insertions(+), 38 deletions(-) create mode 100644 src/lime/tools/LaunchStoryboard.hx create mode 100644 templates/ios/storyboards/splash.storyboard diff --git a/src/lime/tools/HXProject.hx b/src/lime/tools/HXProject.hx index 6738e2ae7..3042c1bf2 100644 --- a/src/lime/tools/HXProject.hx +++ b/src/lime/tools/HXProject.hx @@ -40,6 +40,7 @@ class HXProject extends Script public var javaPaths:Array; public var keystore:Keystore; public var languages:Array; + public var launchStoryboard:LaunchStoryboard; public var libraries:Array; public var libraryHandlers:Map; public var meta:MetaData; @@ -412,6 +413,11 @@ class HXProject extends Script } project.languages = languages.copy(); + + if (launchStoryboard != null) + { + project.launchStoryboard = launchStoryboard.clone(); + } for (library in libraries) { @@ -895,6 +901,15 @@ class HXProject extends Script keystore.merge(project.keystore); } + if (launchStoryboard == null) + { + launchStoryboard = project.launchStoryboard; + } + else + { + launchStoryboard.merge(project.launchStoryboard); + } + languages = ArrayTools.concatUnique(languages, project.languages, true); libraries = ArrayTools.concatUnique(libraries, project.libraries, true); diff --git a/src/lime/tools/ImageHelper.hx b/src/lime/tools/ImageHelper.hx index 7eb9cbce6..8b6e2264f 100644 --- a/src/lime/tools/ImageHelper.hx +++ b/src/lime/tools/ImageHelper.hx @@ -8,6 +8,7 @@ import lime.utils.UInt8Array; #end import lime.tools.Platform; import sys.io.File; +import sys.io.FileSeek; import sys.FileSystem; class ImageHelper @@ -132,6 +133,30 @@ class ImageHelper return null; } + public static function readPNGImageSize(path:String) + { + var toReturn = {width: 0, height: 0}; + var fileInput = File.read(path); + var header = (fileInput.readByte() << 8) | fileInput.readByte(); + + if (header == 0x8950) + { + fileInput.seek(8 + 4 + 4, FileSeek.SeekBegin); + + var width = (fileInput.readByte() << 24) | (fileInput.readByte() << 16) | (fileInput.readByte() << 8) | fileInput.readByte(); + var height = (fileInput.readByte() << 24) | (fileInput.readByte() << 16) | (fileInput.readByte() << 8) | fileInput.readByte(); + + toReturn = { + width: width, + height: height + }; + } + + fileInput.close(); + + return toReturn; + } + public static function resizeImage(image:#if (lime && lime_cffi && !macro) Image #else Dynamic #end, width:Int, height:Int):#if (lime && lime_cffi && !macro) Image #else Dynamic #end { diff --git a/src/lime/tools/LaunchStoryboard.hx b/src/lime/tools/LaunchStoryboard.hx new file mode 100644 index 000000000..975824b42 --- /dev/null +++ b/src/lime/tools/LaunchStoryboard.hx @@ -0,0 +1,66 @@ +package lime.tools; + +import hxp.ObjectTools; + +class LaunchStoryboard +{ + public var assetsPath:String; + public var assets:Array; + public var path:String; + + public var template:String; + public var templateContext:Dynamic; + + public function new () + { + assets = []; + templateContext = {}; + } + + public function clone():LaunchStoryboard + { + var launchStoryboard = new LaunchStoryboard(); + launchStoryboard.assetsPath = assetsPath; + launchStoryboard.assets = assets.copy(); + launchStoryboard.path = path; + launchStoryboard.template = template; + launchStoryboard.templateContext = ObjectTools.copyFields(templateContext, {}); + + return launchStoryboard; + } + + public function merge(launchStoryboard:LaunchStoryboard):Void + { + if (launchStoryboard != null) + { + if (launchStoryboard.assetsPath != null) assetsPath = launchStoryboard.assetsPath; + if (launchStoryboard.assets != null) assets = launchStoryboard.assets; + if (launchStoryboard.path != null) path = launchStoryboard.path; + if (launchStoryboard.template != null) template = launchStoryboard.template; + if (launchStoryboard.templateContext != null) templateContext = launchStoryboard.templateContext; + } + } +} + +class LaunchStoryboardAsset +{ + public var type:String; + + public function new(type:String) + { + this.type = type; + } +} + +class ImageSet extends LaunchStoryboardAsset +{ + public var name:String; + public var width = 0; + public var height = 0; + + public function new(name:String) + { + super("imageset"); + this.name = name; + } +} \ No newline at end of file diff --git a/src/lime/tools/ProjectXMLParser.hx b/src/lime/tools/ProjectXMLParser.hx index 00dfadb55..5696bad41 100644 --- a/src/lime/tools/ProjectXMLParser.hx +++ b/src/lime/tools/ProjectXMLParser.hx @@ -1365,6 +1365,86 @@ class ProjectXMLParser extends HXProject } splashScreens.push(splashScreen); + + case "launchStoryboard": + if (launchStoryboard == null) + { + launchStoryboard = new LaunchStoryboard(); + } + + if (element.has.path) + { + launchStoryboard.path = Path.combine(extensionPath, substitute(element.att.path)); + } + else if (element.has.name) + { + launchStoryboard.path = Path.combine(extensionPath, substitute(element.att.name)); + } + else if (element.has.template) + { + launchStoryboard.template = substitute(element.att.template); + launchStoryboard.templateContext = {}; + + for (attr in element.x.attributes()) + { + if (attr == "assetsPath") continue; + + var valueType = "String"; + var valueName = attr; + + if (valueName.indexOf(":") != -1) + { + valueType = valueName.substring(valueName.lastIndexOf(":") + 1); + valueName = valueName.substring(0, valueName.lastIndexOf(":")); + } + + var stringValue = element.x.get(attr); + var value:Dynamic; + + switch(valueType) + { + case "Int": + value = Std.parseInt(stringValue); + case "RGB": + var rgb:lime.math.ARGB = Std.parseInt(stringValue); + value = {r: rgb.r/255, g: rgb.g/255, b: rgb.b/255}; + case "String": + value = stringValue; + default: + Log.warn("Ignoring unknown value type \"" + valueType + "\" in storyboard configuration."); + value = ""; + } + + Reflect.setField(launchStoryboard.templateContext, valueName, value); + } + } + + if (element.has.assetsPath) + { + launchStoryboard.assetsPath = Path.combine(extensionPath, substitute(element.att.assetsPath)); + } + + for (childElement in element.elements) + { + var isValid = isValidElement(childElement, ""); + + if (isValid) + { + switch(childElement.name) + { + case "imageset": + var name = substitute(childElement.att.name); + var imageset = new LaunchStoryboard.ImageSet(name); + + if (childElement.has.width) + imageset.width = Std.parseInt(substitute(childElement.att.width)); + if (childElement.has.height) + imageset.height = Std.parseInt(substitute(childElement.att.height)); + + launchStoryboard.assets.push(imageset); + } + } + } case "icon": var path = ""; diff --git a/templates/ios/storyboards/splash.storyboard b/templates/ios/storyboards/splash.storyboard new file mode 100644 index 000000000..fb7de84a6 --- /dev/null +++ b/templates/ios/storyboards/splash.storyboard @@ -0,0 +1,48 @@ + += 9)::useSafeAreas="YES"::end:: colorMatched="YES" initialViewController="01J-lp-oVM"> + + + + + + + ::if (deploymentVersion.major >= 9)::::end:: + + + + + + + + + + + + + + + ::if portrait::::end:: + ::if landscape:: + ::end:: + ::if ipad::::end:: + + + ::if bg:: + + ::else:: + + ::end:: + ::if (deploymentVersion.major >= 9)::::end:: + + + + + + + + + ::foreach imagesets:: + + ::end:: + + diff --git a/templates/ios/template/{{app.file}}.xcodeproj/project.pbxproj b/templates/ios/template/{{app.file}}.xcodeproj/project.pbxproj index e013be960..4dccacde8 100644 --- a/templates/ios/template/{{app.file}}.xcodeproj/project.pbxproj +++ b/templates/ios/template/{{app.file}}.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 4257533F1A5EFD8C004AA45B /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4257533E1A5EFD8C004AA45B /* Images.xcassets */; }; 792E75C91C6C876900D01DE0 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 792E75C81C6C876900D01DE0 /* GameController.framework */; }; 792E75C91C6C876900D01DE1 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 792E75C81C6C876900D01DE1 /* CoreText.framework */; }; + ::if (IOS_LAUNCH_STORYBOARD != null)::D099CA9021A64C87003837AD /* ::IOS_LAUNCH_STORYBOARD::.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D099CA8F21A64C86003837AD /* ::IOS_LAUNCH_STORYBOARD::.storyboard */; };::end:: ::ADDL_PBX_BUILD_FILE:: /* End PBXBuildFile section */ @@ -58,6 +59,7 @@ 6662F3920A0E282007F4E3E /* ::APP_FILE::.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = "::APP_FILE::.entitlements"; path = "::APP_FILE::/::APP_FILE::.entitlements"; sourceTree = ""; }; 792E75C81C6C876900D01DE0 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; }; 792E75C81C6C876900D01DE1 /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; }; + ::if (IOS_LAUNCH_STORYBOARD != null)::D099CA8F21A64C86003837AD /* ::IOS_LAUNCH_STORYBOARD::.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "::APP_FILE::/::IOS_LAUNCH_STORYBOARD::.storyboard"; sourceTree = ""; };::end:: ::ADDL_PBX_FILE_REFERENCE:: 8D1107310486CEB800E47090 /* ::APP_FILE::-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "::APP_FILE::/::APP_FILE::-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; @@ -153,6 +155,7 @@ isa = PBXGroup; children = ( 4257533E1A5EFD8C004AA45B /* Images.xcassets */, + ::if (IOS_LAUNCH_STORYBOARD != null)::D099CA8F21A64C86003837AD /* ::IOS_LAUNCH_STORYBOARD::.storyboard */,::end:: 8D1107310486CEB800E47090 /* ::APP_FILE::/::APP_FILE::-Info.plist */, 1E2E17A5131E8B5D0048F3C7 /* ::APP_FILE::/assets */, ); @@ -229,6 +232,7 @@ buildActionMask = 2147483647; files = ( 1E2E17AD131E8B5D0048F3C7 /* ::APP_FILE::/assets in Resources */, + ::if (IOS_LAUNCH_STORYBOARD != null)::D099CA9021A64C87003837AD /* ::IOS_LAUNCH_STORYBOARD::.storyboard in Resources */,::end:: 4257533F1A5EFD8C004AA45B /* Images.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -345,7 +349,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + ::if (IOS_LAUNCH_STORYBOARD == null)::ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;::end:: CODE_SIGN_ENTITLEMENTS = "::APP_FILE::/::APP_FILE::.entitlements"; ::if DEVELOPMENT_TEAM_ID::DEVELOPMENT_TEAM = ::DEVELOPMENT_TEAM_ID::;::end:: ENABLE_BITCODE = ::if (ENABLE_BITCODE)::YES::else::NO::end::; @@ -401,7 +405,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + ::if (IOS_LAUNCH_STORYBOARD == null)::ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;::end:: CODE_SIGN_ENTITLEMENTS = "::APP_FILE::/::APP_FILE::.entitlements"; ::if DEVELOPMENT_TEAM_ID::DEVELOPMENT_TEAM = ::DEVELOPMENT_TEAM_ID::;::end:: ENABLE_BITCODE = ::if (ENABLE_BITCODE)::YES::else::NO::end::; diff --git a/templates/ios/template/{{app.file}}/{{app.file}}-Info.plist b/templates/ios/template/{{app.file}}/{{app.file}}-Info.plist index 31ac9a744..e9f1ae721 100644 --- a/templates/ios/template/{{app.file}}/{{app.file}}-Info.plist +++ b/templates/ios/template/{{app.file}}/{{app.file}}-Info.plist @@ -28,6 +28,8 @@ ::APP_BUILD_NUMBER:: LSRequiresIPhoneOS + ::if (IOS_LAUNCH_STORYBOARD != null)::UILaunchStoryboardName + ::IOS_LAUNCH_STORYBOARD::::end:: UIRequiredDeviceCapabilities ::foreach REQUIRED_CAPABILITY::::name:: diff --git a/tools/platforms/IOSPlatform.hx b/tools/platforms/IOSPlatform.hx index 457bdf6a9..e893933d5 100644 --- a/tools/platforms/IOSPlatform.hx +++ b/tools/platforms/IOSPlatform.hx @@ -21,8 +21,10 @@ import lime.tools.DeploymentHelper; import lime.tools.HXProject; import lime.tools.Icon; import lime.tools.IconHelper; +import lime.tools.ImageHelper; import lime.tools.IOSHelper; import lime.tools.Keystore; +import lime.tools.LaunchStoryboard; import lime.tools.Platform; import lime.tools.PlatformTarget; import lime.tools.ProjectHelper; @@ -501,54 +503,147 @@ class IOSPlatform extends PlatformTarget } } - var splashSizes:Array = [ - {name: "Default.png", w: 320, h: 480}, // iPhone, portrait {name: "Default@2x.png", w: 640, h: 960}, // iPhone Retina, portrait - {name: "Default-568h@2x.png", w: 640, h: 1136}, // iPhone 5, portrait {name: "Default-667h@2x.png", w: 750, h: 1334}, // iPhone 6, portrait - {name: "Default-736h@3x.png", w: 1242, h: 2208}, // iPhone 6 Plus, portrait {name: "Default-Landscape.png", w: 1024, h: 768}, // iPad, landscape - {name: "Default-Landscape@2x.png", w: 2048, h: 1536}, // iPad Retina, landscape {name: "Default-736h-Landscape@3x.png", w: 2208, h: 1242}, - // iPhone 6 Plus, landscape - {name: "Default-Portrait.png", w: 768, h: 1024}, // iPad, portrait {name: "Default-Portrait@2x.png", w: 1536, h: 2048}, - // iPad Retina, portrait - {name: "Default-812h@3x.png", w: 1125, h: 2436}, // iPhone X, portrait - {name: "Default-Landscape-812h@3x.png", w: 2436, h: 1125} // iPhone X, landscape - ]; - - var splashScreenPath = Path.combine(projectDirectory, "Images.xcassets/LaunchImage.launchimage"); - System.mkdir(splashScreenPath); - - for (size in splashSizes) + if (project.launchStoryboard != null) { - var match = false; - - for (splashScreen in project.splashScreens) + var sb = project.launchStoryboard; + + var assetsPath = sb.assetsPath; + var imagesets = []; + + for (asset in sb.assets) { - if (splashScreen.width == size.w && splashScreen.height == size.h && Path.extension(splashScreen.path) == "png") + switch (asset.type) { - System.copyFile(splashScreen.path, Path.combine(splashScreenPath, size.name)); - match = true; + case "imageset": + var imageset = cast(asset, ImageSet); + imagesets.push(imageset); + + var imagesetPath = Path.combine(projectDirectory, "Images.xcassets/" + imageset.name + ".imageset"); + System.mkdir(imagesetPath); + + var baseImageName = Path.withoutExtension(imageset.name); + + var imageScales = ["1x", "2x", "3x"]; + var images = []; + for (scale in imageScales) + { + var filename = baseImageName + (scale == "1x" ? "" : "@"+scale) + ".png"; + if (FileSystem.exists(Path.combine(assetsPath, filename))) + { + images.push({idiom: "universal", filename: filename, scale: scale}); + System.copyFile(Path.combine(assetsPath, filename), Path.combine(imagesetPath, filename)); + + if (imageset.width == 0 || imageset.height == 0) + { + var dim = ImageHelper.readPNGImageSize(Path.combine(assetsPath, filename)); + var scaleValue = Std.parseInt(scale.charAt(0)); + imageset.width = Std.int(dim.width / scaleValue); + imageset.height = Std.int(dim.height / scaleValue); + } + } + } + + var contents = { + images: images, + info: { + version: "1", + author: "xcode" + } + }; + + File.saveContent(Path.combine(imagesetPath, "Contents.json"), Json.stringify(contents)); + + default: + } } - - if (!match) + + if (sb.template != null) { - var imagePath = Path.combine(splashScreenPath, size.name); - - if (!FileSystem.exists(imagePath)) + sb.templateContext.imagesets = []; + + for (imageset in imagesets) { - #if (lime && lime_cffi && !macro) - Log.info("", " - \x1b[1mGenerating image:\x1b[0m " + imagePath); - - var image = new Image(null, 0, 0, size.w, size.h, (0xFF << 24) | (project.window.background & 0xFFFFFF)); - var bytes = image.encode(PNG); - - File.saveBytes(imagePath, bytes); - #end + sb.templateContext.imagesets.push({ + name: imageset.name, + width: imageset.width, + height: imageset.height, + }); } + + var deployment:String = context.DEPLOYMENT; + var parts = deployment.split("."); + var major = Std.parseInt(parts[0]); + var minor = parts.length >= 2 ? Std.parseInt(parts[1]) : 0; + var patch = parts.length >= 3 ? Std.parseInt(parts[2]) : 0; + + Reflect.setField(sb.templateContext, "deploymentVersion", { + major: major, + minor: minor, + patch: patch, + code: Std.parseInt("0x" + major + minor + patch) + }); + + System.copyFileTemplate(project.templatePaths, "ios/storyboards/" + sb.template, projectDirectory + sb.template, sb.templateContext, true, true); + context.IOS_LAUNCH_STORYBOARD = Path.withoutExtension(sb.template); + } + else + { + System.copyFile(sb.path, projectDirectory + Path.withoutDirectory(sb.path)); + context.IOS_LAUNCH_STORYBOARD = Path.withoutDirectory(Path.withoutExtension(sb.path)); } } + else + { + var splashSizes:Array = [ + {name: "Default.png", w: 320, h: 480}, // iPhone, portrait {name: "Default@2x.png", w: 640, h: 960}, // iPhone Retina, portrait + {name: "Default-568h@2x.png", w: 640, h: 1136}, // iPhone 5, portrait {name: "Default-667h@2x.png", w: 750, h: 1334}, // iPhone 6, portrait + {name: "Default-736h@3x.png", w: 1242, h: 2208}, // iPhone 6 Plus, portrait {name: "Default-Landscape.png", w: 1024, h: 768}, // iPad, landscape + {name: "Default-Landscape@2x.png", w: 2048, h: 1536}, // iPad Retina, landscape {name: "Default-736h-Landscape@3x.png", w: 2208, h: 1242}, + // iPhone 6 Plus, landscape + {name: "Default-Portrait.png", w: 768, h: 1024}, // iPad, portrait {name: "Default-Portrait@2x.png", w: 1536, h: 2048}, + // iPad Retina, portrait + {name: "Default-812h@3x.png", w: 1125, h: 2436}, // iPhone X, portrait + {name: "Default-Landscape-812h@3x.png", w: 2436, h: 1125} // iPhone X, landscape + ]; - context.HAS_LAUNCH_IMAGE = true; + var splashScreenPath = Path.combine(projectDirectory, "Images.xcassets/LaunchImage.launchimage"); + System.mkdir(splashScreenPath); + for (size in splashSizes) + { + var match = false; + + for (splashScreen in project.splashScreens) + { + if (splashScreen.width == size.w && splashScreen.height == size.h && Path.extension(splashScreen.path) == "png") + { + System.copyFile(splashScreen.path, Path.combine(splashScreenPath, size.name)); + match = true; + } + } + + if (!match) + { + var imagePath = Path.combine(splashScreenPath, size.name); + + if (!FileSystem.exists(imagePath)) + { + #if (lime && lime_cffi && !macro) + Log.info("", " - \x1b[1mGenerating image:\x1b[0m " + imagePath); + + var image = new Image(null, 0, 0, size.w, size.h, (0xFF << 24) | (project.window.background & 0xFFFFFF)); + var bytes = image.encode(PNG); + + File.saveBytes(imagePath, bytes); + #end + } + } + } + + context.HAS_LAUNCH_IMAGE = true; + } + System.mkdir(projectDirectory + "/resources"); System.mkdir(projectDirectory + "/haxe/build");