[iOS翻译]如何在没有Xcode的情况下编写iOS应用 您所在的位置:网站首页 苹果的xcode怎么用 [iOS翻译]如何在没有Xcode的情况下编写iOS应用

[iOS翻译]如何在没有Xcode的情况下编写iOS应用

2023-04-14 16:44| 来源: 网络整理| 查看: 265

你知道你有iOS IDE选项吗?

原文地址:betterprogramming.pub/writing-ios…

原文作者:alex-nekrasov.medium.com/

发布时间:2020年4月13日-22分钟阅读

没有Xcode IDE的iOS开发(图片来源:作者)

Xcode是苹果公司推出的IDE(集成开发环境),有着悠久的历史。它是iOS开发的原生环境,支持Objective-C和Swift,包括XIB和Storyboard编辑器、编译器、调试器和开发所需的一切。

为什么有人要在没有Xcode的情况下开发iOS应用呢?有几个可能的原因。

Xcode 消耗大量内存,在许多 Mac 上运行缓慢。 Xcode有很多bug。苹果会修复它们,但会用新功能增加新的bug。 Xcode只能在macOS中工作,这让来自其他平台的开发者感到不舒服。

不幸的是,苹果做了一切可能的事情来阻止开发者使用其他平台,iOS模拟器是Xcode应用包的一部分,编辑Storyboards没有替代品,而且没有Xcode,要做一个完整的签名构建是非常复杂的。

所以,要说明的是,有些事情是不可能的,所以不要指望克服这些限制。

原生iOS应用只能在Mac上开发。你甚至可以在Windows或Linux中编写代码,但你不能在那里构建和签署。 非原生平台,比如Flutter或React Native,没有Mac也无法进行iOS构建。 Storyboards只能在Xcode中编辑,所以没有Xcode的开发意味着没有Storyboards的开发。 iOS开发的其他IDE都需要Xcode。你不需要运行它,但你应该安装它。 签署和上传应用程序到App Store(或Test Flight)可以从命令行完成(见下文),但你需要安装Xcode。

我并不是说Xcode绝对不能用。正好相反,它是制作iOS应用最简单的方法。但如果你面临上面提到的其中一个困难,或者只是想尝试一些新的东西,这个故事就适合你。

原生或非原生?

iOS的应用可以是原生的,也可以是非原生的。其实,这不是非黑即白,中间有很多选择。

绝对的原生应用是用Objective-C或Swift编写的。有些部分可以用C或C++编写。用户界面通常以Storyboard或XIB文件的形式呈现。

绝对的非原生应用只是用WKWebView(UIWebView)包装的网站。现在苹果拒绝这类应用,但在过去,这类应用相当普遍。显然,这样的应用不需要和Xcode有太多的交互,用一个WebView创建一个UIViewController几乎不是问题,即使你在网上租了Mac,没有任何Xcode的经验。

其他所有的选择都在中间。它们使用本地组件,但代码通常是用非本地语言编写的。例如,Flutter使用Dart,React Native - JavaScript,Unity - C#。所有这些框架都使用自己的开发环境,但它们都会导出一个Xcode项目。你需要用......Xcode来构建它发布。通常,这不是一个问题。文档中包含了对从未见过Xcode的人的一步步指导。

在Android Studio中编写Flutter应用程序并不是这个故事的主题。这是一个默认的选项,所以我们不会在这上面浪费时间。我们将讨论如何在没有Xcode的情况下开发、测试和发布原生iOS应用。

我们有哪些问题?

让我们来看看为什么没有Xcode编写iOS应用不是那么容易。

创建项目--Xcode将你的工作保存在项目和工作空间中。工作空间只是一组相互关联的项目。它们都是文件夹,里面有文本文件。这些文件的格式是专有的,但它们很大,有很多生成的id,所以它们不应该被人类编辑。 用户界面--Storyboard是另一种专有格式。你不能在Xcode之外编辑它。 构建和测试--有命令行编译器swiftc、gcc和llvm,但如何制作一个可运行的iOS应用呢? iOS项目

任何比 "Hello world "更复杂的应用都有不止一个文件。在iOS的情况下,即使是 "Hello world "也有多个文件。

可以在模拟器中运行的iOS应用至少有两个组件。

可执行的二进制文件 Info.plist文件

可在真实的iOS设备上运行的完整应用有更多的组件,但我们稍后会讨论这个问题。

要创建一个二进制程序,你至少需要两个项目。

源代码 构建脚本

iOS应用可以通过命令行来构建;例如,使用make。我们稍后再看如何做。

更舒适的构建iOS应用的方法是使用Xcode项目。我发现只有一个应用程序(除了Xcode)可以创建这样的项目--AppCode。它是JetBrain的一个应用,类似于IDEA和Android Studio,但针对苹果的特定开发,macOS和iOS。它只在macOS上运行,而且是付费的(2020年4月起8.9美元/月或89美元/年)。

警告!AppCode不能编辑Storyboards。AppCode不能编辑Storyboards。当你尝试时,它会打开Xcode。而且它需要安装Xcode,否则,它不会构建应用程序。

Xcode项目的结构

前面我说过,Xcode项目是不应该手动编辑的。

xcodeproj是一个文件夹,里面包含一个文件和几个文件夹。

这个文件就是project.pbxproj。它包含了项目的所有信息,是最关键的文件。

警告! 如果你要编辑一个现有的项目,请做一个备份。

project.bbxproj 是一个类似于plist的文件。这种格式来自于NeXTSTEP平台。现代的plist是XML,但project.bbxproj更类似于JSON(尽管它不是JSON)。

project.bbxproj主要是一组对象。每个对象都有一个唯一的标识符(一个96位的数字或一个24个十六进制字符的字符串)。对象可以是源文件、链接框架、构建阶段等。对象可以是一个组,包含其他对象。对象可以有属性和类型。其中有一个对象是根对象,它的类型是 PBXProject。它的类型是 PBXProject。

如果在所有警告之后,你决定编辑 project.pbxproj 文件,你可以使用 Visual Studio Code 的 Syntax Xcode Project Data 扩展名。这里你可以找到文件格式的详细说明。

除了project.bbxproj,Xcode项目还包含几个文件夹。它们都是选项。

project.xcworkspace是一个只包含一个项目的工作区。当您在Xcode中打开一个项目文件时,它会自动创建,它包含构建模式、断点信息和其他数据,这些数据不是项目的一部分。

xcuserdata是一个文件夹,包含不同用户的个人数据。如果你是唯一的开发者,里面就只有一个文件夹。这个文件夹是可选的,可以从 Git 和其他仓库中排除。

xcshareddata是一个包含用户之间共享数据的文件夹;例如,方案。

如果你不使用Xcode,你只需要project.bbxproj。

用make从控制台构建iOS应用

说实话,我觉得用这种方式做项目太头疼了。比起手动做这么多步骤,用Xcode来解决你的分歧(反正你需要安装它来签署应用)更容易。但理论上的可能性很有趣,所以我们来深入了解一下。

首先,让我们得到一个SDK路径。

xcrun --sdk iphonesimulator --show-sdk-path... 复制代码

结果会是这样的。

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.4.sdk 复制代码

在Makefile中,你可以粘贴xcrun的输出,或者使用该命令作为脚本的一部分。

SDKROOT:=$(shell xcrun --sdk iphonesimulator --show-sdk-path) 复制代码

我们来做一个app的目标。

app: main.m clang -isysroot $(SDKROOT) -framework Foundation -framework UIKit -o MakeTest.app/$@ $^ 复制代码

clang是Xcode包中的一个标准编译器。

我们添加了两个框架。

Foundation UIKit

输出文件是MakeTest.app/app。

$@是一个自动变量,它的值是一个目标的名称。在我们的例子中,它是app。$^ 是另一个自动变量。它的值是一个完整的依赖列表--在这个实验中是main.m。

而clean target:

.PHONY: clean clean: rm MakeTest.app/app 复制代码

最后,让我们声明app是主目标。

default: app 复制代码

这是一个完整的Makefile。

default: app .PHONY: clean clean: rm MakeTest.app/app SDKROOT:=$(shell xcrun --sdk iphonesimulator --show-sdk-path) app: main.m clang -isysroot $(SDKROOT) -framework Foundation -framework UIKit -o MakeTest.app/$@ $^ 复制代码

如果你从来没有用过make来构建项目的话:

make app 构建一个app目标 make clean清理(删除app文件)。

由于我们声明了一个默认的目标,我们可以直接使用make命令来构建项目。

下一步是创建一个app文件夹。是的,iOS应用就是一个文件夹。

mkdir MakeTest.app 复制代码

在MakeTest.app里面应该有两个文件。

app是一个二进制文件,我们用make命令构建它。 Info.plist(大写的I开头)是一个项目的属性列表。iOS设备或模拟器需要知道要运行哪个二进制文件,它有哪个版本,以及其他数据。

这是我们的Info.plist。如果你运行自己的测试,你可以改变一些字段。

CFBundleDevelopmentRegion en CFBundleDisplayName MakeTest CFBundleExecutable app CFBundleIdentifier com.test.make CFBundleInfoDictionaryVersion 6.0 CFBundleName MakeTest CFBundlePackageType APPL CFBundleShortVersionString 1.0.0 CFBundleSignature MAKE CFBundleVersion 1 LSRequiresIPhoneOS UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight 复制代码

最后一个文件是main.m. 在传统的iOS项目中,有三个不同的文件:

main.m AppDelegate.h AppDelegate.m

这只是一个组织的问题,不是一个严格的规则。由于所有的组件都非常小,我们把它们放在一起。

这里是App的主要功能。它的唯一目的是进入主循环。我们还传递了一个应用程序委托的名称--AppDelegate。

int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } 复制代码

如果你用Xcode或AppCode创建一个项目,这个函数将自动生成。

不要忘记将UIKit包含到你的项目的所有Objective-C文件中。

#import 复制代码

如果你来自Swift,你可能不知道在Objective-C(以及C++)中,每个类都必须被声明和定义。

类的声明。

@interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end 复制代码

类定义。

@implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(id)options { CGRect mainScreenBounds = [[UIScreen mainScreen] bounds]; self.window = [[UIWindow alloc] initWithFrame:mainScreenBounds]; UIViewController *viewController = [[UIViewController alloc] init]; viewController.view.backgroundColor = [UIColor whiteColor]; viewController.view.frame = mainScreenBounds; UILabel *label = [[UILabel alloc] initWithFrame:mainScreenBounds]; [label setText:@"Wow! I was built with clang and make!"]; [viewController.view addSubview: label]; self.window.rootViewController = viewController; [self.window makeKeyAndVisible]; return YES; } @end 复制代码

它创建了一个窗口,视图控制器,并在其中心创建了一个标签。我不会详细介绍,因为它与Xcode之外的编码无关。这只是iOS编程。

应用委托、视图控制器和其他组件可以放在单独的文件中。甚至,这是一种推荐的做法。从技术上讲,基于Makefile的项目可以使用与普通Xcode项目相同的结构。

下面是它的样子。

在没有Xcode的情况下构建的iOS项目,在iPhone 11模拟器中运行。

main.m的完整源代码。

#import @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(id)options { CGRect mainScreenBounds = [[UIScreen mainScreen] bounds]; self.window = [[UIWindow alloc] initWithFrame:mainScreenBounds]; UIViewController *viewController = [[UIViewController alloc] init]; viewController.view.backgroundColor = [UIColor whiteColor]; viewController.view.frame = mainScreenBounds; UILabel *label = [[UILabel alloc] initWithFrame:mainScreenBounds]; [label setText:@"Wow! I was built with clang and make!"]; [viewController.view addSubview: label]; self.window.rootViewController = viewController; [self.window makeKeyAndVisible]; return YES; } @end int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } 复制代码

那么,接下来是什么?

权限文件--如果你需要向你的iOS项目添加任何功能,你需要一个权限文件。这是一个带有键值对的plist文件,描述了它所使用的iOS设备或苹果用户账户的哪些资源或服务。你可以在苹果的文档中找到更多的细节。

-你可以看到,在我们的例子中,应用程序并没有采取全屏。它可以通过添加Launch图片或适当的大小,或Launch屏幕Storyboard来解决。

制作两个目标:针对iOS设备和模拟器。iOS构建需要签名,我们稍后会讲到。

App图标可以作为Assets.xcassets文件夹的一部分添加,也可以作为一组PNG文件添加(在Info.plist中有引用)。Assets.xcassets是一个包含应用资产的文件夹。我们稍后再来讨论它。

如果你有任何用这种方式构建商业iOS应用的经验,请留言。我将很乐意根据你的经验加入更多信息。

构建用户界面

我找到了四种不用Xcode、不用Storyboards构建用户界面的方法。

SwiftUI--苹果公司新推出的UI构建器可以在任何文本编辑器中进行编辑。例如,Visual Studio Code(VS Code)有一个编写Swift代码的插件。而且是免费的。缺点是,你需要iOS 13才能用SwiftUI运行应用。几年后,它将不再是一个缺点,但现在放弃对iOS 12和更早版本的支持还为时过早。

从代码中创建组件--任何组件都可以从Objective-C或Swift代码中创建,无需任何UI设计师。它很长,很不舒服,但非常通用。代码可以在任何地方编写,你的应用程序甚至可以在第一台iPhone上运行(如果你设法为这样一个老设备构建它)。

外部工具--有一些工具可以将设计(例如,用Sketch做的)转换为原生iOS代码(用Objective-C或Swift)。这种工具的一个例子是Supernova。它是付费的,就像其他具有类似功能的工具一样。

外部库--这些库允许你编写短代码来构建原生UI。它们是免费的,但你需要学习使用它们。这几乎就像学习一门新的编程语言一样。例如:LinkedIn的LayoutKit。

没有完美的解决方案。他们每个人都有优点和缺点,所以要看你用什么。我认为假以时日,SwiftUI将成为iOS最流行的UI构建方式。但如果你需要更快地发布你的应用,你最好使用其他方式。无论如何,这是你的选择。

SwiftUI

SwiftUI是苹果公司推出的一个新框架,应该是要取代UIKit和Storyboards的。

优点是

它是iOS系统的原生产品 苹果会支持它,推广它,并激励开发者使用它。 SwiftUI的代码可以在任何文本编辑器中编写。 SwiftUI可以和UIKit混合在同一个布局中。

缺点

SwiftUI只能针对iOS 13或更高版本。它不会在iOS 12上运行。 布局预览和模拟只能在Xcode中工作。没有AppCode、VS Code或任何其他代码编辑器的插件。 让我们看看SwiftUI是如何工作的。 import SwiftUI struct ContentView: View { var body: some View { VStack { MapView() .edgesIgnoringSafeArea(.top) .frame(height: 300) CircleImage() .offset(x: 0, y: -130) .padding(.bottom, -130) VStack(alignment: .leading) { Text("Turtle Rock") .font(.title) HStack(alignment: .top) { Text("Joshua Tree National Park") .font(.subheadline) Spacer() Text("California") .font(.subheadline) } } .padding() Spacer() } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } 复制代码

这个例子是从苹果官方教程中借来的。

这里你可以找到另一个详细的SwiftUI教程。

从代码中创建UIKit组件

这是一种绝对通用的创建布局的方法。你可以创建组件,添加约束,基本上可以做任何事情。但每个组件都需要完全初始化。而且当你看不到自己在做什么的时候,使用约束条件工作就是一场噩梦。预览布局只能在Simulator或iOS设备上进行,而且每次修正都需要重新启动应用程序。

这就是它的工作原理。

override func viewDidLoad() { super.viewDidLoad() let continueButton = UIButton() continueButton.setTitle("Continue", for: .normal) continueButton.setTitleColor(UIColor.blue, for: .normal) continueButton.frame = CGRect(x: 16, y: 32, width: UIScreen.main.bounds.width - 32, height: 44) continueButton.addTarget(self, action: #selector(pressedButton(_:)), for: .touchUpInside) self.view.addSubview(continueButton) } @objc func pressedButton(_ button: UIButton) { // Do something } 复制代码

这段代码应该在UIViewController子类中。

每个UI组件都应该这样创建.它们可以嵌套,就像在Storyboard中一样。Storyboards的所有功能和UIKit的所有组件都可以通过代码获得,包括表格、集合等。

外部工具

设计师们使用Sketch、Adobe XD、Zeplin或其他工具进行iOS布局。如果你能直接将它们导出到iOS应用中,那不是很好吗?当然可以。而且这是可能的,但不是免费的。

我发现有几个工具允许这样做。

Supernova - 从20美元/月 Sketch的代码生成插件--47欧元/年;加上Sketch,99美元。 Zeplin拥有Swift和Objective-C扩展,可以将样式导出到iOS代码。一个项目免费,然后17美元/月起。

请注意,价格不是固定的,随时都有可能改变。

这些工具/插件的结果是原生的iOS代码,Swift或Objective-C。

比方说,我们有一个用Sketch做的设计。在这个例子中,我下载了废物管理App草图资源这个文件。

Sketch

Sketch应用打开后有警告(版本不匹配),但这不是问题。下一步--超新星。

Supernova Studio

Supernova Studio有导入Sketch或Adobe XD文件的功能。

将Sketch导入Supernova Studio

导入成功了,但是有一些小毛病。例如,后面的箭头(见截图)。另外,在预览时,文字与按钮重叠,但应该用约束或UIScrollView来解决。

超新星的iOS屏幕设计

箭头的问题可以通过使用光栅图片或者手动来解决。我们来看看如何导出到iOS代码。文件→导出到iOS。尝试导出时,显示出我缺少字体的列表。

缺少的字体

好吧,我们先不管它。系统字体没问题。

我只选择了iPhone的导出,而且只选择了纵向,这样就不会深究细节了。

从超新星出口

语言只能是Swift。对我来说,选择最新的才是合理的。目前,是Swift 5。UI必须是Code。其他的选择还有Storyboard和XIB,但它们只能由Xcode打开,所以这不是我们的选择。

导出过程只用了不到一分钟的时间。它生成了一个带有Podfile的Xcode项目。Podfile几乎是空的,它只有一个骨架,但没有依赖关系。

platform :ios, '11.0' inhibit_all_warnings! target 'Waste-management-app-musafarouk' do # Add all your pods here # Supernova Pods end post_install do |installer| installer.pods_project.targets.each do |target| puts "#{target.name}" end end 复制代码

无论如何,让我们安装Pods来生成一个工作空间。

项目由Supernova Studio生成

每个屏幕都是一个独立的类,是UIViewController的子类,位于不同的文件夹中。字体、图像和其他资产也被导出。而且没有Storyboards。这意味着我们可以用AppCode打开它。

下面是一个Supernova Studio导出的例子,文件HomeActivityNavViewController。

// // HomeActiveNavViewController.swift // Waste-management-app-musafarouk // // Created by Supernova. // Copyright © 2018 Supernova. All rights reserved. // // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- // MARK: - Import import UIKit // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- // MARK: - Implementation class HomeActiveNavViewController: UIViewController { // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- // MARK: - Properties var homeActiveNavView: UIView! var snazzyImage5ImageView: UIImageView! var lineImageView: UIImageView! var rectangleView: UIView! var rectangleTwoView: UIView! var group6View: UIView! var acmeWasteDisposalLabel: UILabel! var group4View: UIView! var birninKebbiCreLabel: UILabel! var icLocationOn24pxImageView: UIImageView! var routingLabel: UILabel! var group5View: UIView! var stopLabel: UILabel! var targetView: UIView! var outlinedUiMapTargetImageView: UIImageView! var binView: UIView! var recyclingBinImageView: UIImageView! var locationView: UIView! var ovalView: UIView! var ovalTwoView: UIView! var ovalThreeView: UIView! var pathImageView: UIImageView! var ovalImageView: UIImageView! var rectangleThreeView: UIView! var group3View: UIView! var menuLeftImageView: UIImageView! var groupImageView: UIImageView! private var allGradientLayers: [CAGradientLayer] = [] // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- // MARK: - Lifecycle override public func viewDidLoad() { super.viewDidLoad() self.setupComponents() self.setupLayout() self.setupUI() self.setupGestureRecognizers() self.setupLocalization() // Do any additional setup after loading the view, typically from a nib. } override public func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // Navigation bar, if any self.navigationController?.setNavigationBarHidden(true, animated: true) } // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- // MARK: - Setup private func setupComponents() { // Setup homeActiveNavView self.view.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 1) /* #FFFFFF */ self.view.translatesAutoresizingMaskIntoConstraints = true // Setup snazzyImage5ImageView self.snazzyImage5ImageView = UIImageView() self.snazzyImage5ImageView.backgroundColor = UIColor.clear self.snazzyImage5ImageView.image = UIImage(named: "snazzy-image-5") self.snazzyImage5ImageView.contentMode = .scaleAspectFill self.view.addSubview(self.snazzyImage5ImageView) self.snazzyImage5ImageView.translatesAutoresizingMaskIntoConstraints = false // Setup lineImageView self.lineImageView = UIImageView() self.lineImageView.backgroundColor = UIColor.clear self.lineImageView.image = UIImage(named: "line") self.lineImageView.contentMode = .center self.view.addSubview(self.lineImageView) self.lineImageView.translatesAutoresizingMaskIntoConstraints = false // Setup rectangleView self.rectangleView = UIView(frame: .zero) let rectangleViewGradient = CAGradientLayer() rectangleViewGradient.colors = [UIColor(red: 0, green: 0, blue: 0, alpha: 0.5).cgColor /* #000000 */, UIColor(red: 0, green: 0, blue: 0, alpha: 0.5).cgColor /* #000000 */, UIColor.clear.cgColor] rectangleViewGradient.locations = [0, 0, 1] rectangleViewGradient.startPoint = CGPoint(x: 0.5, y: 1) rectangleViewGradient.endPoint = CGPoint(x: 0.5, y: 0) rectangleViewGradient.frame = self.rectangleView.bounds self.rectangleView.layer.insertSublayer(rectangleViewGradient, at: 0) self.allGradientLayers.append(rectangleViewGradient) self.view.addSubview(self.rectangleView) self.rectangleView.translatesAutoresizingMaskIntoConstraints = false // Setup rectangleTwoView self.rectangleTwoView = UIView(frame: .zero) self.rectangleTwoView.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.2).cgColor /* #000000 */ self.rectangleTwoView.layer.shadowOffset = CGSize(width: 0, height: 10) self.rectangleTwoView.layer.shadowRadius = 20 self.rectangleTwoView.layer.shadowOpacity = 1 self.rectangleTwoView.backgroundColor = UIColor(red: 0.2, green: 0.216, blue: 0.294, alpha: 1) /* #33374B */ self.rectangleTwoView.layer.cornerRadius = 6 self.rectangleTwoView.layer.masksToBounds = true self.view.addSubview(self.rectangleTwoView) self.rectangleTwoView.translatesAutoresizingMaskIntoConstraints = false // Setup group6View self.group6View = UIView(frame: .zero) self.group6View.backgroundColor = UIColor.clear self.view.addSubview(self.group6View) self.group6View.translatesAutoresizingMaskIntoConstraints = false // Setup acmeWasteDisposalLabel self.acmeWasteDisposalLabel = UILabel() self.acmeWasteDisposalLabel.numberOfLines = 0 let acmeWasteDisposalLabelAttrString = NSMutableAttributedString(string: "Acme waste disposal", attributes: [ .font : UIFont.systemFont(ofSize: 20), .foregroundColor : UIColor(red: 1, green: 1, blue: 1, alpha: 1), .kern : 0.5, .paragraphStyle : NSMutableParagraphStyle(alignment: .left, lineHeight: 25, paragraphSpacing: 0) ]) self.acmeWasteDisposalLabel.attributedText = acmeWasteDisposalLabelAttrString self.acmeWasteDisposalLabel.backgroundColor = UIColor.clear self.group6View.addSubview(self.acmeWasteDisposalLabel) self.acmeWasteDisposalLabel.translatesAutoresizingMaskIntoConstraints = false // Setup group4View self.group4View = UIView(frame: .zero) self.group4View.backgroundColor = UIColor.clear self.group6View.addSubview(self.group4View) self.group4View.translatesAutoresizingMaskIntoConstraints = false // Setup birninKebbiCreLabel self.birninKebbiCreLabel = UILabel() self.birninKebbiCreLabel.numberOfLines = 0 let birninKebbiCreLabelAttrString = NSMutableAttributedString(string: "25, Birnin Kebbi Cres, Garki, Abuja • 2km", attributes: [ .font : UIFont.systemFont(ofSize: 12), .foregroundColor : UIColor(red: 1, green: 1, blue: 1, alpha: 1), .kern : 0.3, .paragraphStyle : NSMutableParagraphStyle(alignment: .left, lineHeight: 20, paragraphSpacing: 0) ]) self.birninKebbiCreLabel.attributedText = birninKebbiCreLabelAttrString self.birninKebbiCreLabel.backgroundColor = UIColor.clear self.birninKebbiCreLabel.alpha = 0.5 self.group4View.addSubview(self.birninKebbiCreLabel) self.birninKebbiCreLabel.translatesAutoresizingMaskIntoConstraints = false // Setup icLocationOn24pxImageView self.icLocationOn24pxImageView = UIImageView() self.icLocationOn24pxImageView.backgroundColor = UIColor.clear self.icLocationOn24pxImageView.image = UIImage(named: "ic-location-on-24px-2") self.icLocationOn24pxImageView.contentMode = .center self.group4View.addSubview(self.icLocationOn24pxImageView) self.icLocationOn24pxImageView.translatesAutoresizingMaskIntoConstraints = false // Setup routingLabel self.routingLabel = UILabel() self.routingLabel.numberOfLines = 0 let routingLabelAttrString = NSMutableAttributedString(string: "Routing…", attributes: [ .font : UIFont.systemFont(ofSize: 14), .foregroundColor : UIColor(red: 1, green: 1, blue: 1, alpha: 1), .kern : 0.44, .paragraphStyle : NSMutableParagraphStyle(alignment: .left, lineHeight: 25, paragraphSpacing: 0) ]) self.routingLabel.attributedText = routingLabelAttrString self.routingLabel.backgroundColor = UIColor.clear self.routingLabel.alpha = 0.5 self.view.addSubview(self.routingLabel) self.routingLabel.translatesAutoresizingMaskIntoConstraints = false // Setup group5View self.group5View = UIView(frame: .zero) self.group5View.backgroundColor = UIColor(red: 0.627, green: 0, blue: 0, alpha: 1) /* #A00000 */ self.group5View.layer.cornerRadius = 4 self.group5View.layer.masksToBounds = true self.view.addSubview(self.group5View) self.group5View.translatesAutoresizingMaskIntoConstraints = false // Setup stopLabel self.stopLabel = UILabel() self.stopLabel.numberOfLines = 0 let stopLabelAttrString = NSMutableAttributedString(string: "Stop", attributes: [ .font : UIFont.systemFont(ofSize: 14), .foregroundColor : UIColor(red: 1, green: 1, blue: 1, alpha: 1), .kern : 0.44, .paragraphStyle : NSMutableParagraphStyle(alignment: .center, lineHeight: nil, paragraphSpacing: 0) ]) self.stopLabel.attributedText = stopLabelAttrString self.stopLabel.backgroundColor = UIColor.clear self.group5View.addSubview(self.stopLabel) self.stopLabel.translatesAutoresizingMaskIntoConstraints = false // Setup targetView self.targetView = UIView(frame: .zero) self.targetView.layer.shadowColor = UIColor(red: 0.141, green: 0.149, blue: 0.2, alpha: 1).cgColor /* #242633 */ self.targetView.layer.shadowOffset = CGSize(width: 0, height: 11) self.targetView.layer.shadowRadius = 23 self.targetView.layer.shadowOpacity = 1 self.targetView.backgroundColor = UIColor(red: 0.141, green: 0.149, blue: 0.2, alpha: 1) /* #242633 */ self.targetView.layer.cornerRadius = 25 self.targetView.layer.masksToBounds = true self.view.addSubview(self.targetView) self.targetView.translatesAutoresizingMaskIntoConstraints = false // Setup outlinedUiMapTargetImageView self.outlinedUiMapTargetImageView = UIImageView() self.outlinedUiMapTargetImageView.backgroundColor = UIColor.clear self.outlinedUiMapTargetImageView.image = UIImage(named: "outlined-ui-map-target") self.outlinedUiMapTargetImageView.contentMode = .center self.targetView.addSubview(self.outlinedUiMapTargetImageView) self.outlinedUiMapTargetImageView.translatesAutoresizingMaskIntoConstraints = false // Setup binView self.binView = UIView(frame: .zero) self.binView.layer.borderColor = UIColor(red: 1, green: 1, blue: 1, alpha: 1).cgColor /* #FFFFFF */ self.binView.layer.borderWidth = 5 self.binView.backgroundColor = UIColor(red: 0.031, green: 0.451, blue: 0.239, alpha: 1) /* #08733D */ self.binView.layer.cornerRadius = 10 self.binView.layer.masksToBounds = true self.view.addSubview(self.binView) self.binView.translatesAutoresizingMaskIntoConstraints = false // Setup recyclingBinImageView self.recyclingBinImageView = UIImageView() self.recyclingBinImageView.backgroundColor = UIColor.clear self.recyclingBinImageView.image = UIImage(named: "recycling-bin") self.recyclingBinImageView.contentMode = .center self.binView.addSubview(self.recyclingBinImageView) self.recyclingBinImageView.translatesAutoresizingMaskIntoConstraints = false // Setup locationView self.locationView = UIView(frame: .zero) self.locationView.backgroundColor = UIColor.clear self.view.addSubview(self.locationView) self.locationView.translatesAutoresizingMaskIntoConstraints = false // Setup ovalView self.ovalView = UIView(frame: .zero) self.ovalView.backgroundColor = UIColor(red: 0.031, green: 0.451, blue: 0.239, alpha: 1) /* #08733D */ self.ovalView.layer.cornerRadius = 49 self.ovalView.layer.masksToBounds = true self.ovalView.alpha = 0.1 self.locationView.addSubview(self.ovalView) self.ovalView.translatesAutoresizingMaskIntoConstraints = false // Setup ovalTwoView self.ovalTwoView = UIView(frame: .zero) self.ovalTwoView.backgroundColor = UIColor(red: 0.031, green: 0.451, blue: 0.239, alpha: 1) /* #08733D */ self.ovalTwoView.layer.cornerRadius = 65 self.ovalTwoView.layer.masksToBounds = true self.ovalTwoView.alpha = 0.05 self.locationView.addSubview(self.ovalTwoView) self.ovalTwoView.translatesAutoresizingMaskIntoConstraints = false // Setup ovalThreeView self.ovalThreeView = UIView(frame: .zero) self.ovalThreeView.layer.shadowColor = UIColor(red: 0.031, green: 0.451, blue: 0.239, alpha: 1).cgColor /* #08733D */ self.ovalThreeView.layer.shadowOffset = CGSize(width: 0, height: 5) self.ovalThreeView.layer.shadowRadius = 10 self.ovalThreeView.layer.shadowOpacity = 1 self.ovalThreeView.backgroundColor = UIColor(red: 0.031, green: 0.451, blue: 0.239, alpha: 1) /* #08733D */ self.ovalThreeView.layer.cornerRadius = 16 self.ovalThreeView.layer.masksToBounds = true self.locationView.addSubview(self.ovalThreeView) self.ovalThreeView.translatesAutoresizingMaskIntoConstraints = false // Setup pathImageView self.pathImageView = UIImageView() self.pathImageView.backgroundColor = UIColor.clear self.pathImageView.image = UIImage(named: "path") self.pathImageView.contentMode = .center self.locationView.addSubview(self.pathImageView) self.pathImageView.translatesAutoresizingMaskIntoConstraints = false // Setup ovalImageView self.ovalImageView = UIImageView() self.ovalImageView.backgroundColor = UIColor.clear self.ovalImageView.image = UIImage(named: "oval-5") self.ovalImageView.contentMode = .center self.view.addSubview(self.ovalImageView) self.ovalImageView.translatesAutoresizingMaskIntoConstraints = false // Setup rectangleThreeView self.rectangleThreeView = UIView(frame: .zero) let rectangleThreeViewGradient = CAGradientLayer() rectangleThreeViewGradient.colors = [UIColor(red: 0, green: 0, blue: 0, alpha: 1).cgColor /* #000000 */, UIColor(red: 0, green: 0, blue: 0, alpha: 0.5).cgColor /* #000000 */, UIColor.clear.cgColor] rectangleThreeViewGradient.locations = [0, 0, 1] rectangleThreeViewGradient.startPoint = CGPoint(x: 0.5, y: 0) rectangleThreeViewGradient.endPoint = CGPoint(x: 0.5, y: 1) rectangleThreeViewGradient.frame = self.rectangleThreeView.bounds self.rectangleThreeView.layer.insertSublayer(rectangleThreeViewGradient, at: 0) self.allGradientLayers.append(rectangleThreeViewGradient) self.view.addSubview(self.rectangleThreeView) self.rectangleThreeView.translatesAutoresizingMaskIntoConstraints = false // Setup group3View self.group3View = UIView(frame: .zero) self.group3View.backgroundColor = UIColor.clear self.view.addSubview(self.group3View) self.group3View.translatesAutoresizingMaskIntoConstraints = false // Setup menuLeftImageView self.menuLeftImageView = UIImageView() self.menuLeftImageView.backgroundColor = UIColor.clear self.menuLeftImageView.image = UIImage(named: "menu-left") self.menuLeftImageView.contentMode = .center self.group3View.addSubview(self.menuLeftImageView) self.menuLeftImageView.translatesAutoresizingMaskIntoConstraints = false // Setup groupImageView self.groupImageView = UIImageView() self.groupImageView.backgroundColor = UIColor.clear self.groupImageView.image = UIImage(named: "group-48") self.groupImageView.contentMode = .center self.group3View.addSubview(self.groupImageView) self.groupImageView.translatesAutoresizingMaskIntoConstraints = false } private func setupUI() { self.extendedLayoutIncludesOpaqueBars = true self.navigationController?.setNavigationBarHidden(true, animated: true) } private func setupGestureRecognizers() { } private func setupLocalization() { } // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- // MARK: - Layout override public func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() for layer in self.allGradientLayers { layer.frame = layer.superlayer?.frame ?? CGRect.zero } } private func setupLayout() { // Setup layout for components // Setup homeActiveNavView // Setup snazzyImage5ImageView self.snazzyImage5ImageView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: -303).isActive = true self.snazzyImage5ImageView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 222).isActive = true self.snazzyImage5ImageView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0).isActive = true // Setup lineImageView self.lineImageView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -98).isActive = true self.lineImageView.topAnchor.constraint(equalTo: self.rectangleThreeView.bottomAnchor, constant: 34).isActive = true // Setup rectangleView self.rectangleView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true self.rectangleView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true self.rectangleView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0).isActive = true self.rectangleView.heightAnchor.constraint(equalToConstant: 90).isActive = true // Setup rectangleTwoView self.rectangleTwoView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 17).isActive = true self.rectangleTwoView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -18).isActive = true self.rectangleTwoView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -35).isActive = true self.rectangleTwoView.heightAnchor.constraint(equalToConstant: 216).isActive = true // Setup group6View self.group6View.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 42).isActive = true self.group6View.bottomAnchor.constraint(equalTo: self.routingLabel.topAnchor, constant: -16).isActive = true self.group6View.widthAnchor.constraint(equalToConstant: 280).isActive = true self.group6View.heightAnchor.constraint(equalToConstant: 68).isActive = true // Setup acmeWasteDisposalLabel self.acmeWasteDisposalLabel.leadingAnchor.constraint(equalTo: self.group6View.leadingAnchor, constant: 0).isActive = true self.acmeWasteDisposalLabel.trailingAnchor.constraint(equalTo: self.group6View.trailingAnchor, constant: -87).isActive = true self.acmeWasteDisposalLabel.topAnchor.constraint(equalTo: self.group6View.topAnchor, constant: -1).isActive = true // Setup group4View self.group4View.leadingAnchor.constraint(equalTo: self.group6View.leadingAnchor, constant: 0).isActive = true self.group4View.topAnchor.constraint(equalTo: self.acmeWasteDisposalLabel.bottomAnchor, constant: 4).isActive = true self.group4View.widthAnchor.constraint(equalToConstant: 220).isActive = true self.group4View.heightAnchor.constraint(equalToConstant: 40).isActive = true // Setup birninKebbiCreLabel self.birninKebbiCreLabel.leadingAnchor.constraint(equalTo: self.icLocationOn24pxImageView.trailingAnchor, constant: 8).isActive = true self.birninKebbiCreLabel.trailingAnchor.constraint(equalTo: self.group4View.trailingAnchor, constant: 0).isActive = true self.birninKebbiCreLabel.topAnchor.constraint(equalTo: self.group4View.topAnchor, constant: -3).isActive = true self.birninKebbiCreLabel.widthAnchor.constraint(equalToConstant: 205).isActive = true // Setup icLocationOn24pxImageView self.icLocationOn24pxImageView.leadingAnchor.constraint(equalTo: self.group4View.leadingAnchor, constant: 0).isActive = true self.icLocationOn24pxImageView.topAnchor.constraint(equalTo: self.group4View.topAnchor, constant: 4).isActive = true // Setup routingLabel self.routingLabel.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 42).isActive = true self.routingLabel.bottomAnchor.constraint(equalTo: self.group5View.topAnchor, constant: -30).isActive = true // Setup group5View self.group5View.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 0).isActive = true self.group5View.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -51).isActive = true self.group5View.widthAnchor.constraint(equalToConstant: 290).isActive = true self.group5View.heightAnchor.constraint(equalToConstant: 45).isActive = true // Setup stopLabel self.stopLabel.centerXAnchor.constraint(equalTo: self.group5View.centerXAnchor, constant: 0).isActive = true self.stopLabel.centerYAnchor.constraint(equalTo: self.group5View.centerYAnchor, constant: 0).isActive = true // Setup targetView self.targetView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -18).isActive = true self.targetView.topAnchor.constraint(equalTo: self.rectangleThreeView.bottomAnchor, constant: 401).isActive = true self.targetView.widthAnchor.constraint(equalToConstant: 50).isActive = true self.targetView.heightAnchor.constraint(equalToConstant: 50).isActive = true // Setup outlinedUiMapTargetImageView self.outlinedUiMapTargetImageView.leadingAnchor.constraint(equalTo: self.targetView.leadingAnchor, constant: 12).isActive = true self.outlinedUiMapTargetImageView.trailingAnchor.constraint(equalTo: self.targetView.trailingAnchor, constant: -12).isActive = true self.outlinedUiMapTargetImageView.centerYAnchor.constraint(equalTo: self.targetView.centerYAnchor, constant: 0).isActive = true // Setup binView self.binView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -78).isActive = true self.binView.topAnchor.constraint(equalTo: self.rectangleThreeView.bottomAnchor, constant: 5).isActive = true self.binView.widthAnchor.constraint(equalToConstant: 50).isActive = true self.binView.heightAnchor.constraint(equalToConstant: 50).isActive = true // Setup recyclingBinImageView self.recyclingBinImageView.leadingAnchor.constraint(equalTo: self.binView.leadingAnchor, constant: 16).isActive = true self.recyclingBinImageView.trailingAnchor.constraint(equalTo: self.binView.trailingAnchor, constant: -16).isActive = true self.recyclingBinImageView.centerYAnchor.constraint(equalTo: self.binView.centerYAnchor, constant: 0).isActive = true // Setup locationView self.locationView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 0).isActive = true self.locationView.topAnchor.constraint(equalTo: self.binView.bottomAnchor, constant: 204).isActive = true self.locationView.widthAnchor.constraint(equalToConstant: 130).isActive = true self.locationView.heightAnchor.constraint(equalToConstant: 130).isActive = true // Setup ovalView self.ovalView.leadingAnchor.constraint(equalTo: self.locationView.leadingAnchor, constant: 16).isActive = true self.ovalView.trailingAnchor.constraint(equalTo: self.locationView.trailingAnchor, constant: -16).isActive = true self.ovalView.centerYAnchor.constraint(equalTo: self.locationView.centerYAnchor, constant: 0).isActive = true self.ovalView.heightAnchor.constraint(equalToConstant: 98).isActive = true // Setup ovalTwoView self.ovalTwoView.leadingAnchor.constraint(equalTo: self.locationView.leadingAnchor, constant: 0).isActive = true self.ovalTwoView.trailingAnchor.constraint(equalTo: self.locationView.trailingAnchor, constant: 0).isActive = true self.ovalTwoView.centerYAnchor.constraint(equalTo: self.locationView.centerYAnchor, constant: 0).isActive = true self.ovalTwoView.heightAnchor.constraint(equalToConstant: 130).isActive = true // Setup ovalThreeView self.ovalThreeView.centerXAnchor.constraint(equalTo: self.locationView.centerXAnchor, constant: 0).isActive = true self.ovalThreeView.centerYAnchor.constraint(equalTo: self.locationView.centerYAnchor, constant: 0).isActive = true self.ovalThreeView.widthAnchor.constraint(equalToConstant: 32).isActive = true self.ovalThreeView.heightAnchor.constraint(equalToConstant: 32).isActive = true // Setup pathImageView self.pathImageView.centerXAnchor.constraint(equalTo: self.locationView.centerXAnchor, constant: 0).isActive = true self.pathImageView.centerYAnchor.constraint(equalTo: self.locationView.centerYAnchor, constant: 0).isActive = true // Setup ovalImageView self.ovalImageView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -43).isActive = true self.ovalImageView.topAnchor.constraint(equalTo: self.targetView.bottomAnchor, constant: 40).isActive = true // Setup rectangleThreeView self.rectangleThreeView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true self.rectangleThreeView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true self.rectangleThreeView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0).isActive = true self.rectangleThreeView.heightAnchor.constraint(equalToConstant: 90).isActive = true // Setup group3View self.group3View.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 17).isActive = true self.group3View.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -18).isActive = true self.group3View.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 53).isActive = true self.group3View.heightAnchor.constraint(equalToConstant: 27).isActive = true // Setup menuLeftImageView self.menuLeftImageView.leadingAnchor.constraint(equalTo: self.group3View.leadingAnchor, constant: 0).isActive = true self.menuLeftImageView.centerYAnchor.constraint(equalTo: self.group3View.centerYAnchor, constant: 0).isActive = true // Setup groupImageView self.groupImageView.trailingAnchor.constraint(equalTo: self.group3View.trailingAnchor, constant: 0).isActive = true self.groupImageView.centerYAnchor.constraint(equalTo: self.group3View.centerYAnchor, constant: 0).isActive = true } // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- // MARK: - Status Bar override public var prefersStatusBarHidden: Bool { return true } override public var preferredStatusBarStyle: UIStatusBarStyle { return .default } } 复制代码

这个文件包含 "版权 © 2018 Supernova." 这是一个有趣的细节,因为我在2020年3月做了这个导出。

项目构建并成功运行。下面是它的样子。

iPhone 11模拟器中的应用

很明显,这些按钮不能用,因为我从来没有在Supernova Studio中添加逻辑。但这是可能的。按钮没有绿色背景。在我看来,这些问题并不重要,可以在导出后的代码中轻松修复。在Supernova Studio和IDE(Xcode或App Code)中都可以添加逻辑。

外部库

有不同的布局框架。它们时隐时现,所以如果你选择这种方法,只要找到最近更新的框架(在去年内),并且支持Swift 5,并且有你需要的功能。

我们来回顾一下其中最流行的一个--LinkedIn的LayoutKit。

let image = SizeLayout(width: 50, height: 50, config: { imageView in imageView.image = UIImage(named: "earth.jpg") }) let label = LabelLayout(text: "Hello World!", alignment: .center) let stack = StackLayout( axis: .horizontal, spacing: 4, sublayouts: [image, label]) let insets = UIEdgeInsets(top: 4, left: 4, bottom: 4, right: 8) let helloWorld = InsetLayout(insets: insets, sublayout: stack) helloWorld.arrangement().makeViews(in: rootView) 复制代码

这个例子是从LayoutKit官方网站上借来的。

它创建标准的UIKit组件(像所有框架一样)的方式和我们之前做的一样,但它有两个额外的功能。

代码更短、更清晰。 它增加了自动调整组件大小的简单方法。

我想回顾的另一个框架是SnapKit。有时,即使在基于Storyboard的项目中,你也需要从代码中创建组件。在布局里面添加一个有约束的组件,同时又要保持组件尺寸的更新,这是相当复杂的。

SnapKit可以帮助使用代码来添加约束。

import SnapKit class MyViewController: UIViewController { lazy var box = UIView() override func viewDidLoad() { super.viewDidLoad() self.view.addSubview(box) box.backgroundColor = .green box.snp.makeConstraints { (make) -> Void in make.width.height.equalTo(50) make.center.equalTo(self.view) } } } 复制代码

本例借鉴了SnapKit官网的内容。

总结

iOS UI创建方法对比

无论你选择什么,请不要忘记,你的布局应该符合苹果的人机界面指南。

资产

大多数iOS应用都有一个专门的文件夹,里面有资产。它通常是一个名为Assets.xcassets的文件夹。如果你创建一个新的Xcode项目,这个文件夹会自动创建。

它包含不同类型的资产:图片、颜色、数据、AR资源、贴纸包等。最受欢迎的资产是图像集。图像集除了图像,还有重要的元数据。例如,图像集可以为不同的屏幕尺寸、设备类型、分辨率提供不同的图像。集合中的图像可以渲染为原始图像,也可以渲染为模板。模板图像的颜色可以通过色调属性来改变。

资产文件夹中有json文件来存储元数据。这些文件的名称都是一样的--Contents.json。根目录Contents.json通常看起来像这样。

{ "info" : { "version" : 1, "author" : "xcode" } } 复制代码

我看不出有什么理由去改变这个文件。内部文件夹内的文件更有意思。这是AppIcon.appiconset内部的一个Contents.json文件的例子。

{ "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "[email protected]", "scale" : "2x" }, ... { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "[email protected]", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } 复制代码

"信息 "部分也是如此。"images "包含了不同设备和分辨率的图标图像数组。属性。

size - 图标大小,单位为点 idiom--设备类型 filename - 文件的名称。文件应该在同一个文件夹中。 scale -设备比例(视网膜屏幕设备为2倍或3倍,低分辨率设备为1倍)。

图标集的Contents.json文件有类似的结构。例如

{ "images" : [ { "idiom" : "universal", "filename" : "button_back.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "[email protected]", "scale" : "2x" }, { "idiom" : "universal", "filename" : "[email protected]", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } 复制代码

其他资产类型我就不说了,不在范围之内。它们比较高级,并不是所有项目都会用到。

资产文件夹可以在Xcode和AppCode中进行编辑。另外,如果你从一些网站下载图片集,比如素材图标,你会得到里面有Contents.json的图片集文件夹。在所有其他情况下,你需要手动创建json文件。

使用iOS模拟器工作

比方说,你建立了一个应用程序,并得到了一个二进制文件(实际上,它是一个文件夹,但里面有一个二进制文件)。你如何在iOS模拟器上运行它?

首先,你需要运行它。

AppCode可以帮你运行它,它在这方面和Xcode非常相似。它支持代码调试,甚至可以在物理设备上运行。

如果你得到一个带有iOS应用的文件夹,你需要按照三个步骤进行。

手动运行iOS模拟器。 将应用文件夹拖到运行iOS模拟器中。 在虚拟屏幕上找到它并运行它。

要运行iOS模拟器,请打开终端应用程序并运行此命令。

open -a Simulator.app 复制代码

要选择设备型号和iOS版本,使用菜单 "文件"→"打开设备 "或 "硬件"→"设备"(在旧版本中)。

调试

iOS模拟器有一个秘密。所有在其中运行的应用程序实际上都是在你的Mac上运行的x86_64应用程序(在某种沙箱中)。这意味着你可以在本地调试iOS应用,就像调试任何macOS应用一样。

首先,运行lldb。

lldb 复制代码

第二,附加到一个进程。

(lldb) process attach --pid 12345 复制代码

或。

(lldb) process attach --name MyApp 复制代码

你可以在Activity Monitor应用程序中找到你的应用程序,该应用程序安装在所有Mac上。在那里你可以找到你的应用程序的pid(进程id)。

如果你不知道如何使用lldb,这里有文档。

应用程序签名

您完成了您的应用程序,在模拟器中进行了测试,并发现和修复了错误。现在是时候在真实设备上进行测试并上线了。

首先,有几件事你需要知道。

iOS应用和macOS应用一样,都是使用扩展app的文件夹。 要发布iOS应用,你需要创建一个名为ipa的存档。这是一个zip压缩包,app文件夹在Payload文件夹里面。 在制作ipa存档之前,你需要对iOS应用进行签名。 要签署你的iOS应用,你需要有一个Apple开发者账户。您可以在 Apple 开发者门户网站上创建一个。 只有经过签名的应用程序才能在物理iOS设备上运行,即使是你自己的iPhone。 你应该有一个证书和一个供应配置文件来签署一个应用程序。Xcode会自动创建它们,但如果你手动签署应用程序,你必须自己申请它们。 在你的plist文件中关于你的应用程序的信息(bundle id,entitlements)和你的provisioning profile应该匹配。

这听起来很复杂,实际上确实如此。让我们一步步回顾这个过程,因为如果没有应用签名,之前所有的努力都没有多大意义。

在这个例子中,我将使用上文 "用make从控制台构建iOS应用 "一节中前面构建的一个应用。它的名字叫MakeTest。

用make打造的iOS应用

你的应用程序应该像你在上图中看到的那样。白色标志意味着你不能在macOS中运行它。

步骤0. 编译一个应用程序

在我们开始之前,你应该有一个适用于armv7和arm64的应用程序。如果你从Xcode或其他平台构建,只要选择一个合适的目标。如果你使用我们之前构建的例子,在Makefile中做一些修改。

替换: SDKROOT:=$(shell xcrun --sdk iphonesimulator --show-sdk-path) 复制代码

与。

SDKROOT:=$(shell xcrun --sdk iphoneos --show-sdk-path) 复制代码 将构建命令从。 clang -isysroot $(SDKROOT) -framework Foundation -framework UIKit -o MakeTest.app/$@ $^ 复制代码

到。

clang -isysroot $(SDKROOT) -arch armv7 -arch arm64 -framework Foundation -framework UIKit -o MakeTest.app/$@ $^ 复制代码

这将产生一个所谓的 "胖 "二进制,有两个架构。这正是我们所需要的。

如果你在这个阶段得到任何错误,你可能有Xcode的问题。安装它,如果你还没有这样做。运行它,Xcode会在每次更新后安装一个命令行工具。

第1步:创建证书 创建一个证书

要创建证书,请打开这个链接:developer.apple.com/account/res… "+"按钮,然后添加一个新的 "Apple Distribution "证书。我在这里就不详细介绍了。这个过程相当简单,你可以找到很多教程和手册。准备好后下载安装。

在Keychain Access应用中检查你的证书。你应该在下一步使用完整的证书名称。

钥匙链与苹果分销证书

第二步。代码签名

打开终端应用程序,并将当前目录改为你的工作文件夹(包含MakeTest.app)。

cd full_path 复制代码

类型。

codesign -s "Apple Distribution: Your Account Name (TEAM_ID)" MakeTest.app 复制代码

几秒钟后,你会看到MakeTest.app里面的文件夹_CodeSignature。这是一个数字签名。而这也是不要和你不信任的人分享你的证书的原因。这个签名证明了你开发的应用。如果有人盗用你的证书,发布一个经过签名的应用,做一些非法的事情,你的账号就会被封。

第三步。创建一个供应配置文件

打开Apple Provisioning Portal:developer.apple.com/account/res…

单击 "+"按钮。你可以生成几种类型的配置文件。

iOS应用开发--只能用于自己的设备。 AdHoc - 可以分配给有限数量的设备,包括在配置文件中。 App Store - 可以上传到App Store。

在这个例子中,让我们生成一个特设的配置文件,并创建一个应用程序安装的链接。

在下一步,你需要选择你的应用ID。可能你的ID还不在列表中。

选择应用ID

如果是这样,请前往标识符列表并添加一个新的 App ID:developer.apple.com/account/res… ID(Bundle ID)应与您的 Info.plist 中的 ID 相匹配。您可以选择您在应用程序中使用的功能。对于这个测试,你不需要选择任何东西。其中一些可以预先选择,不要更改。

回到配置文件生成,选择你的应用程序ID。在下一个屏幕上,你需要选择一个证书。它应该与你在上一步使用的证书完全相同。如果你有几个日期不同的证书,请检查你的Keychain中的证书。比较到期日期。它应该是相同的或有一天的差异。

在下一步,你需要选择设备。如果你以前使用过Xcode,你的设备可能已经注册了。如果没有,在这里手动注册它。

要注册一个新设备,你需要输入它的UDID(Unique Device ID)。有两种方法可以得到它。

你可以把你的设备用线连接到你的PC/Mac上,然后在iTunes中找到UDID。在macOS Catalina中,没有iTunes,但你可以在Finder中找到你的设备。在设备名称下,你会看到一些附加信息。点击它一次或几次,直到你看到UDID。请注意,序列号和UDID是不同的。

你可以使用一个服务,如get.udid.io 或类似的服务。在你的iOS设备上打开它,并按照说明进行操作。 当你的设备注册后,回到配置文件生成,检查一个或多个设备,并生成配置文件。在最后一步,你需要输入一个配置文件名称。通常,我使用应用程序名称加上 "AdHoc"。准备好后,下载配置文件。

第4步。配置

要将供应配置文件添加到您的应用程序中,只需将其复制到app文件夹并将其重命名为embedded.mobileprovision。

然后再次签署它。

codesign -f -s "Apple Distribution: Your Account Name (TEAM_ID)" MakeTest.app 复制代码

如果你之前签过字,请添加-f标志(强制)。

第五步。权属

首先,我们来生成权益文件。

security cms -D -i Payload/MakeTest.app/embedded.mobileprovision 复制代码

这将会向控制台输出一个包括 entitlements 的大结构。将这个结构保存在一个以.entitlements结尾的文件中。按照下面的结构。

application-identifier TEAM_ID.com.test.make keychain-access-groups TEAM_ID.* get-task-allow com.apple.developer.team-identifier TEAM_ID 复制代码

您应该拥有与前面所有步骤相同的应用程序ID,并且team_id与您的Apple开发者帐户相匹配。

你不应该在 Payload 中包含 entitlements 文件。相反,将它作为codesign命令的一个参数。

codesign -f -s "Apple Distribution: Your Account Name (TEAM_ID)" --entitlements 'MakeTest.entitlements' Payload/MakeTest.app 复制代码 第六步. 创建ipa

要在物理设备上安装你的应用,你需要创建一个ipa存档。让我们看看如何做。

mkdir Payload cp -r MakeTest.app Payload zip -r MakeTest.ipa Payload 复制代码

我们在这里!我们有一个档案 - MakeTest.ipa。我们有一个档案 - MakeTest.ipa。

第7步. 分发

要发布你的应用,我推荐Diawi。Diawi(开发与内部应用无线安装)是一项服务,允许你上传你的ipa(或Android的apk),并获得一个链接和二维码。你把这个链接(和/或二维码)发送给设备所有者(iOS设备的UDID应该在你创建的provisioning profile中),他们就可以通过点击几下来安装。

问题是,在当前的配置下,你甚至无法上传它,这让我们进入第8步。

第8步:更新Info.plist 更新Info.plist并排除故障

当你在Xcode中进行构建时,它会在签署之前向Info.plist添加一些字段。

警告!你应该在任何更改后更新你的应用程序签名,包括Info.plist中的更改。

这两个字段对于上传发布版本是必要的。

CFBundleSupportedPlatforms iPhoneOS MinimumOSVersion 10.0 复制代码

版本号可能不同,这取决于你的目标iOS版本。

当您添加这些字段时,上传将成功,但您可能仍然无法安装应用程序。生产版本的Info.plist应该包含兼容设备的信息。

我从Xcode生成的ipa中复制了这些值。其中有些是不必要的,但不会造成任何伤害。

BuildMachineOSBuild 19D76 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild 17B102 DTPlatformName iphoneos DTPlatformVersion 13.2 DTSDKBuild 17B102 DTSDKName iphoneos13.2 DTXcode 1130 DTXcodeBuild 11C504 UIDeviceFamily 1 2 UIRequiredDeviceCapabilities arm64 UIRequiresFullScreen UIStatusBarHidden 复制代码

这是我的Info.plist文件的最终版本。

CFBundleDevelopmentRegion en CFBundleDisplayName MakeTest CFBundleExecutable app CFBundleIdentifier com.test.make CFBundleInfoDictionaryVersion 6.0 CFBundleName MakeTest CFBundlePackageType APPL CFBundleShortVersionString 1.0.0 CFBundleSignature MAKE CFBundleVersion 1 LSRequiresIPhoneOS UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight CFBundleSupportedPlatforms iPhoneOS MinimumOSVersion 10.0 BuildMachineOSBuild 19D76 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild 17B102 DTPlatformName iphoneos DTPlatformVersion 13.2 DTSDKBuild 17B102 DTSDKName iphoneos13.2 DTXcode 1130 DTXcodeBuild 11C504 UIDeviceFamily 1 2 UIRequiredDeviceCapabilities arm64 UIRequiresFullScreen UIStatusBarHidden 复制代码

有可能你的应用还没有安装到你的设备上 There's a possibility that your app won't be installed on your device yet. 很有可能,你不会看到任何错误。但如果你看到了,你如何排除它们的故障?

打开Mac上的控制台应用程序。你的iOS设备应该已经连接上了。

iOS设备控制台

选择你的iOS设备,在过滤器中输入你的应用名称(右上角)。应用安装会产生50条左右的信息,其中大部分信息都不多,所以可能需要几个小时才能找到并解决这个问题。

在上面的截图中,你可以看到一个权利文件的问题。如果你按照所有的步骤进行操作,应该不会出现这种情况,但如果你的情况比较复杂,或者你漏掉了某些步骤,你可以使用Console应用来检查问题所在。

完成

如果你做的一切都正确,你会看到你的应用程序安装在你的设备上。

如果你仍然有问题。

尝试添加应用程序图标。你不需要资产目录。你可以直接添加必要的 png 文件,并将它们添加到你的 Info.plist 中。 添加PkgInfo文件。老实说,我不明白它的用途,但所有Xcode生成的包都包含它。它只有8个字符。APPL????。

结束语

不使用Xcode完全可以创建iOS应用。如果你真的对Xcode有意见,你可能应该使用AppCode。你可以在那里编写代码、构建和调试。它有很多插件,会让这个过程变得更简单。

布局可以用许多不同的方式来制作,使用代码或替代解决方案,如Supernova Studio或Sketch(与插件)。

仅使用终端和文本编辑器制作iOS项目是非常复杂的,但完全可能。只有在真正需要的情况下,才应该使用这种方法;例如,用于自动构建。

下次再见。祝你编码愉快!

通过www.DeepL.com/Translator(免费版)翻译



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有