Python实战社群

Java实战社群

长按识别下方二维码,按需求添加

扫码关注添加客服

进Python社群▲

扫码关注添加客服

进Java社群

作者 | PJHubs 
来源 | http://pjhubs.com/,点击阅读原文查看作者更多文章

前言

iOS14 的 Widget 和 iOS14 之前的 Widget 已经完成了统一,之前老样式的 Widget 只能通过在老版本上进行查看,后续仅支持 iOS14 目前的 Widget。只能使用 SwiftUI 进行开发。

Widget 核心

  • 快速、关联性、个性化

  • 看一眼,就能够获取到重点内容

  • 内容才是最重要的

    • 相册 Widget 注意到的话,会发现展示的照片总是某个时刻下最棒的那一张,而不是最新的。

    • Widget 不是 mini app,应该看作是把 app 的内容在主屏幕的映射关系。官方给出的数据,一般我们会在一天的时间里进入主屏幕超过 90 次,并在主屏幕上短暂停留。

Widget 类型

Widget 有三个尺寸,但不强迫每个尺寸都实现,因为不是所有 app 都适合全尺寸 widget 展示,但推荐都实现(猜测就是要给用户最大自由度。

  • 不能滚动和不能添加开关等其它系统控件。

  • 不支持视频和动图。

  • 小组件并不是在主屏幕上实时展示的。

    • 系统的时钟 Widget 的事实刷新 UI 是个系统级 app 才能拥有的对待。

    • SwiftUI 中对 Text 组件新增了可以实时展示时间的 API。

Text(Date(), style: .time)
  • 不要把小尺寸组件直接拉伸成中或者大尺寸小组件。

  • Small 尺寸组件只能接受单次点击。

  • 小组件内部按照 16pt 设定布局边距。

  • 小组件内部有圆形素材,应该使用 11pt 边距。

  • 小组件内部边界有圆角时要做得跟小组件本身的圆角半径同心。

    • 不同设备上的小组件本身圆角值不一样,不能直接写死圆角值。

    • SwiftUI 中提供了一个圆角容器。

  • 字体官方推荐使用 SF 系列,可自定义。

  • 不要放入 app logo 和 name。

Widget 如何成组?

控制允许用户选择的小组件类型

@main
struct PJWidget: Widget {private let kind: String = "PJWidget"public var body: some WidgetConfiguration {StaticConfiguration(kind: kind, provider: Provider()) { entry inPJWidgetEntryView(entry: entry)}.configurationDisplayName("PJWidget").description("2333").supportedFamilies([.systemSmall, .systemMedium])}
}

在构建 entryView 时,根据当前选择的 widgetFamily 值来返回不同的样式。

struct PJWidgetEntryView: View {var entry: Provider.Entry@Environment(\.widgetFamily) var family@ViewBuildervar body: some View {switch family {case .systemSmall:PJAvatarView(entry.name)default:Text("PJHubs")}}
}

使用 Xcode Widget Extension 模版创建完后,会自动给默认 Widget 加上 @main 修饰符标记出当前 app Widget 的入口。

换句话说,此时我们进入到「Widget 搜索」,找到我们的 app,只会看到一个 Widget。

@main
struct SwiftUIWidgetDemo: Widget {let kind: String = "SwiftUIWidgetDemo"var body: some WidgetConfiguration {IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry inSwiftUIWidgetDemoEntryView(entry: entry)}.configurationDisplayName("西瓜作者-数据日报").description("你的数据精简日报").supportedFamilies([.systemSmall])}
}

@main
struct Widgets: WidgetBundle {@WidgetBundleBuildervar body: some Widget {SwiftUIWidgetDemo()SwiftUIWidgeMediumDemo()SwiftUIWidgeMediumFansDemo()}
}

注意:最多只允许塞入五个 Widget 样式。

Tips:What is @main?

说起 @main 大家可能会先想到之前的 @UIApplicationMain 这个修饰词,说到 @UIApplicationMain 可能又会想到 main.swift 或者 main.m 等等这些文件。总的来说,它们之间是存在某种神秘联系的!我们来写一个简单的 Swift 代码:

class demoSwift {class func test() {print("world!")}
}
demoSwift.test()

此时使用 swiftc demo.swift 后会得到一个可执行文件,看上去 Swift 的语法让新上手的同学令人感到愉快,不会再有类 C 系那种必须写一系列又臭又长的 main 函数初始化流程,但本质上真的不用写了吗?
我们来看看中间代码。

# 查看生成的中间代码
swiftc demo.swift -emit-sil
sil_stage canonicalimport Builtin
import Swift
import SwiftShimsclass demoSwift {class func test()@objc deinitinit()
}// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):%2 = metatype $@thick demoSwift.Type            // user: %4// function_ref static demoSwift.test()%3 = function_ref @$s4demo0A5SwiftC4testyyFZ : $@convention(method) (@thick demoSwift.Type) -> () // user: %4%4 = apply %3(%2) : $@convention(method) (@thick demoSwift.Type) -> ()%5 = integer_literal $Builtin.Int32, 0          // user: %6%6 = struct $Int32 (%5 : $Builtin.Int32)        // user: %7return %6 : $Int32                              // id: %7
} // end sil function 'main'// ... 以下省略

可以看到,所谓的对新人友好都是假的,全都是编译期间 swiftc 做的自动化插入,自动给我们的方法插入了与之前类似的流程,如果我们需要多文件编译依赖,要有一个 main.swift 作为入口文件进行索引其他文件进行编译。

@UIApplicationMain 出现后,我们不再需要 main.swift 文件来做入口切割,可通过自定义类并加上该标记即可,这个好处在 Swift 5.3 中正式推广到语言层面,我们仅需使用 @main 即可标记出 Swift 文件的入口,不再是 Cocoa 特性,进而替代掉了 @UIApplicationMain。

Widget 用户如何配置数据?

Widget 提供了用户可配置数据源的方式,可以通过此类方式来绕过 Widget 成组后最大上限五个的限制。提供两种配置方式

  • StaticConfiguration。用户不可自定义数据源,参考头条 Widget。

  • IntentCOnfiguration。允许用户选择配置,参考下图。

其中 IntentConfiguration 可提供给用户有限的“自由”,自行选择对应 Widget 下需要展示的数据源。利用了基于 Intents.framework 框架实现,并可以直接复用 SiriKit 的功能来达到 Widget 的智能化(后文再叙)。

配置 IntentConfiguration 的步骤如下:

  • 创建对应的 IntentConfiguration 文件。

  • 新增用户可配置的数据类型

  • 配置新增数据类型相关信息

  • 在对应类型的 Widget 中判断数据视图

struct SwiftUIWidgetDemoMediumEntryView : View {var entry: Provider.Entry@ViewBuildervar body: some View {switch entry.configuration.countType {case .money:// 此处需传入数据源MediumWidgetFansView()default:// 此处需传入数据源MediumWidgetView()}}
}

Tips: What is @ViewBuilder

从实际问题看 SwiftUI 和 Combine 编程 已说明,可以前往了解。

Widget 如何跳转到对应的页面?

预览视图

struct Provider: IntentTimelineProvider {// NOTE: 小组件占位视图,第一次添加或 loading 状态中的视图func placeholder(in context: Context) -> SimpleEntry {SimpleEntry(date: Date(), count: 0, image: nil, configuration: ConfigurationIntent())}// NOTE: 第一次添加或小组件第一次被展示时调用func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {let entry = SimpleEntry(date: Date(), count: 0, image: nil, configuration: configuration)completion(entry)}func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {// ...let timeline = Timeline(entries: entries, policy: .atEnd)completion(timeline)}
}

placeholder方法中返回 widget 在初始化 loading 过程中的占位 UI。

  • 每一个 widget 都必须提供。

  • 默认内容展示。

  • 没有任何用户相关数据。

  • 当系统无法显示你的小组件数据时会出现。

  • 无法被告知什么时候应该展示占位图。这是系统行为,系统需要的时候就会要求展示,例如用户更换了 widget 尺寸等。

这个视图是系统行为,只要我们使用的是标准 SwiftUI 组件,会自动根据组件类型,如 Image、Text 结合我们自定义的颜色和背景来自动完成占位图的设置,如果我们不想要的系统自定义的话,也可以在方法中自行返回自定义的占位组件。

注意点:在构建 PlaceHolderView 时,Session 中所给的 isPlaceHoler 通过属性的方式去做已经不行了,得通过以下方式来进行(如果我们需要预览的话):

PJWidgetEntryView(entry: SimpleEntry(date: Date())).redacted(reason: .placeholder)

跳转

Widget 的目的非常简单,目前在 Widget 上所做的事情,全都是为了引导用户可以轻松的点击小组件和通过 deepLink 跳转到我们的 app 中。而从 Widget 跳转到 app 中针对不同类型的 Widget 有共有两种跳转方式。

  • .widgetURL

三种类型的小组件均可使用该方式进行跳转。

struct SmallWidgetView: View {var body: some View {VStack(alignment:.leading) {Text("PJHubs")}.widgetURL(URL(string: "urlschema://pjhubsWidgetURL"))}
}
  • Link

只有 Medium 和 Large 类型的小组件可以使用该方式进行跳转。

Small 类型小组件编译没问题,点击后无回调。

struct MediumWidgetView: View {var body: some View {Link(destination:URL(string: "urlschema://pjhubsLink")!) {VStack(alignment:.leading) {Text("PJHubs")}}}
}
  • SceneDelegate.m 中的内容为:

可以直接复用以往主工程通过消息通知 push 的逻辑流程来打开 widget 上挂载的 schema。

#import "SceneDelegate.h"
#import "WidgetURLViewController.h"
#import "LinkViewController.h"@implementation SceneDelegate- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {if (URLContexts.allObjects.count != 0) {UIOpenURLContext *urlContext = URLContexts.allObjects.firstObject;NSURL *url = urlContext.URL;if ([url.absoluteString isEqualToString:@"urlschema://pjhubsWidgetURL"]) {[self.window.rootViewController presentViewController:[WidgetURLViewController new] animated:YES completion:nil];}if ([url.absoluteString isEqualToString:@"urlschema://pjhubsLink"]) {[self.window.rootViewController presentViewController:[LinkViewController new] animated:YES completion:nil];}}}@end

注意点

  • 调试 Widget Deep Link 跳转时需要切换到 app target 下进行调试,一直在用 Widget target 调,发现断点一直走不进去,才猛的想起来,我在 widget target 里能断在 app target 里才奇了怪了。

  • 更新 widget 的内容后,需要 build 一遍 widget target,然后再回到 app target 走 app 生命周期相关方法。

Tips: 如果 OC View 想要被使用在 SwiftUI 中。

首先创建或确定要被引入 SwiftUI 中的 OC 视图,下文以 OCView 替代。

#import "OCView.h"@implementation OCView- (instancetype)initWithFrame:(CGRect)frame {if (self = [super initWithFrame:frame]) {[self initView];}return self;
}- (void)initView {self.backgroundColor = [UIColor whiteColor];UILabel *textLabel = [[UILabel alloc] init];textLabel.text = @"这是 OC View";textLabel.font = [UIFont systemFontOfSize:30];[textLabel sizeToFit];textLabel.frame = CGRectMake((self.frame.size.width - textLabel.frame.size.width)/2, (self.frame.size.height - textLabel.frame.size.height)/2, textLabel.frame.size.width, textLabel.frame.size.height);[self addSubview:textLabel];
}@end

创建一个 OCWidgetView.swift 文件,用于封装 SwiftUI 视图。

import Foundation
import SwiftUIstruct OCWidgetView: UIViewRepresentable {func makeUIView(context: Context) -> OCView {return OCView()}func updateUIView(_ uiView: OCView, context: Context) {}
}

SwiftUI 中提供了 Coordinator 这个理念来作为 OCView 可能存在的各种 delegate 相关回调事件,在 SwiftUI 中同样可以进行使用,在此不做展开。

此时就可以在 SwiftUI 中引入 OCWidgetView 了!

struct MediumWidgetFansView: View {var body: some View {VStack(alignment:.leading) {OCWidgetView()}}
}

Widget 的 UI 部分只能使用 SwiftUI 框架下的 UI 组件,不能使用任何 UIKit 相关的组件,就算用 SwiftUI 包一层也不行(UIViewRepresentable),强行使用的话,会在 Widget 视图上得到一个黄色背景红叉:

Widget 如何更新数据?

刷新时机

Timeline 刷新

Widget 需要通过 Timeline 来进行数据刷新,但其刷新的时机由系统控制,但有时我们设置了刷新间隔时间也不一定会在该时间点进行刷新。

如果我们完全依赖 Widget 自身的数据更新策略,每次间隔 1s 刷新数据,每次更新时拉取 5 个数据,设置 Timeline policy为 .atEnd,也即当 timeline 中的数据用完后立即拉取下一条数据,则最短也许要 1min 时间才能拉取下一条 timeline(自测)。

  • atEnd: 拉取到最后一个数据后重新拉取。

  • atAfter: 在指定时间后以一定时间间隔拉取数据。

  • never:该 timeline 不需要刷新。

不是都需要一次在 timeline 中构造好多个数据实体,可以一次返回一个,刷新间隔设置为 1min(或其它时间),这样比较适合对数据实时性要求较高的产品。系统并不少按照我们所规定的那样执行逻辑,系统考虑的因素用官方的话语来说,要结合耗电量等等问题综合给到不同 Widget 的刷新时机和此时,但总的来说,经常被查看的 widget 会获得更多的刷新机会。

主动刷新

如果我们想要在 app 内主动同步 widget 上所展示的消息,或在当前时刻必须刷新,如“开言英语” Widget 用户登录前后的 UI 表现不同等,在这种需求背景下,我们可以使用 WidgetKit 的 WidgetCenter API 来完成。

WidgetCenter.shared.reloadAllTimelines()。reloadAllTimelines 方法会重新 load 所属 app 内的所有已配置的 Widget,重新拉取 Timeline。

需要注意的是,WidgetKit 为 Swift Only,想要在 OC 工程中使用该方法刷新 Widget Timeline 得通过 Swift 包一层,且要求 app 处于活跃状态。

import WidgetKit@objcMembers class PJWidgetCenter: NSObject {class func reloadWidgetTimeline() {WidgetCenter.shared.reloadAllTimelines();WidgetCenter.shared.reloadTimelines(ofKind: "What kind of widget?")}
}

支持所有 widget 刷新或某一个 widget。

#import "ViewController.h"
#import "SwiftUIWidget-Swift.h"@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];[PJWidgetCenter reloadWidgetTimeline];
}

数据来源

getTimeline 方法支持异步操作,我们如果需要动态的走网络请求拉取构造 timeline 数据,可以直接丢出一个异步回调。

struct Provider: IntentTimelineProvider {// ...func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {networkHandler {let timeline = Timeline(entries: $0, policy: .atEnd)completion(timeline)}}func networkHandler(completion: @escaping ([SimpleEntry]) -> Void) {URLSession.shared.dataTask(with: URL(string: "http://pjhubs.com")!) { (data, response, error) inlet originalDict = try? JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments) as? NSDictionaryprint(originalDict as Any)completion([SimpleEntry(date: Date(), count: 1234, image: UIImage(named: "avatar")!, configuration: ConfigurationIntent())])}.resume()}
}

注意请求间隔、Timeline 更新时间和数据转换等问题。

数据共享

以在 Widget 展示用户头像举例,在以往的开发经历中,我们都不希望有同步操作阻塞主线程从而造成 app 卡顿,故在 Widget 中我们也会自然而然的在“图片展示”这一环节中套用异步请求资源的思路去做,但这在 Widget 中是不被允许的,我们需要转变一个思路。

以下这种把图片资源延后到 UI 层的做法可以拉取成功,但不会被加载。

struct SmallWidgetView: View {@State var networkImage: UIImage?var body: some View {VStack(alignment:.leading) {HStack {Image(uiImage: self.networkImage ?? UIImage(named: "avatar")!)// ....onAppear(perform: getNetworkImage)                }       // ...}// ...}func getNetworkImage() {URLSession.shared.dataTask(with: URL(string: "https://tu.sioe.cn/gj/qiege/image.jpg")!) { (data, _, _) inself.networkImage = UIImage(data: data!)}.resume()}
}

解决这一问题目前有三种方法但都是一种思路,核心就是把图片的加载过程从异步转化为同步,这个同步的过程可以是在 getTimeline 初始化时间线时,也可以是在构造 Widget UI 层逻辑时。

  • UserDefault

  • FileManager

  • CoreData

以下为在 getTimeline 初始化时间线时的事例:

struct SmallWidgetView: View {var uiImage: UIImage?var body: some View {VStack(alignment:.leading) {HStack {Image(uiImage: uiImage ?? UIImage(named: "avatar")!)// ...}       // ...}// ...}
}
struct Provider: IntentTimelineProvider {// ...func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {var entries: [SimpleEntry] = []let currentDate = Date()for hourOffset in 0 ..< 3 {let entryDate = Calendar.current.date(byAdding: .second, value: hourOffset, to: currentDate)!// NOTE: 在处理 timeline 时就把资源加载好var image: UIImage? = nilif let imageData = try? Data(contentsOf: URL(string: "https://tu.sioe.cn/gj/qiege/image.jpg")!) {image = UIImage(data: imageData)}let entry = SimpleEntry(date: entryDate, count: Int.random(in: 0...100), image: image, configuration: configuration)entries.append(entry)}let timeline = Timeline(entries: entries, policy: .atEnd)completion(timeline)}
}

Widget 在最初放出的 beta 版本中是可以支持图片资源的异步回调的,但后来又改成了目前的这种只能通过同步的方式进行资源获取。

如果出现不同类型的 widget 需要复用图片资源,可以使用系统内轻量级 cache 方法(如:NSCache等)来完成在 A 类型 Widget 下已经加载完成的图片资源,后续用户再手动添加 B 类型 Widget 后可以加速 Widget 渲染。

当我们需要从 app target 传递数据到 widget target 时,可以组成 App Groups,通过 UserDefualt 来完成数据传递,注意两个 target 都需要增加 app groups。

在 app target 中设置测试代码,10s 后刷新 widget 的显示内容,以此来模拟真实 app 中主工程触发某个网络事件,等待延时后同步数据给 Widget。

App target

#import "ViewController.h"
#import "SwiftUIWidget-Swift.h"@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{[PJWidgetCenter reloadWidgetTimeline];});
}
@end

Swift 处理过程(非必需)

import WidgetKit
import Foundation@objcMembers class PJWidgetCenter: NSObject {class func reloadWidgetTimeline() {if let userDefaults = UserDefaults(suiteName: "group.com.pjhubs.swiftuiwidge") {userDefaults.setValue("2333", forKey: "integer")}WidgetCenter.shared.reloadAllTimelines();}
}

Widget

struct Provider: IntentTimelineProvider {@AppStorage("integer", store: UserDefaults(suiteName: "group.com.pjhubs.swiftuiwidge"))var intString: String = ""//...        func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {var entries: [SimpleEntry] = []let currentDate = Date()for hourOffset in 0 ..< 5 {let entryDate = Calendar.current.date(byAdding: .second, value: hourOffset, to: currentDate)!// ...let entry = SimpleEntry(date: entryDate, count: Int(intString)!, image: image, configuration: configuration)entries.append(entry)}let timeline = Timeline(entries: entries, policy: .atEnd)completion(timeline)}
}

Widge 如何在「智能堆叠」中提高展示?

推荐看完 为小组件添加智能和配置 https://developer.apple.com/videos/play/wwdc2020/10194/

基于 iOS12 引入的 Intent.framework,目前有两种提高 Widget 在智能堆叠中展示的办法。

  • 用户行为捐赠(系统推断)

  • 数据源评分展示(评估函数判分)

用户行为捐赠

WWDC20 Session - 为小组件添加智能和配置视频截图。

我们可以把一些自定义的关键组合信息构造出一个 intent 捐赠给系统,通过 Intent.framework,系统不但可以把这些信息传递给我们 app widget 还可以传递到 spotlight 等其它依赖 Intent 的场景从而减少进入特定场景/app 的步骤。

转换成我们的产品视角,当作者每天都在 14 点查看自己的视频播放量这一个指标数据,可以在作者进入到指标页面时,通过构造 Intent 实例进行捐赠给系统,当累计到一定次数(不定)后,系统会在每天用户 14 点前后解锁进入主屏时,在「智能堆叠」Widget 中自动翻滚到我们加入其中的 Widget 并展示出对应的播放量 Widget。

数据源评分展示

如果我们想要在特定时间主动突出小组件在智能堆叠上的展示机会,可以使用「数据源评分展示」策略,在构造 Timeline 时可以给不同的数据实体塞入不同的评分,从而达到在不同时间节点或特定时间节点下的突出展示。

转换成我们的产品视角,当作者新发布了一个视频,可能想要在未来的一天、两天甚至一周内关注视频本身的播放量这一指标,我们可以通过固定分数和持续时间来达到提升展示,从而关闭其它数据源更新时的

struct Provider: IntentTimelineProvider {// ...func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {// ...for hourOffset in 0 ..< 3 {// ...if (hourOffset == 1) {let revelance = TimelineEntryRelevance(score: 2000, duration: 60);let entry = SimpleEntry(date: entryDate, count: 2000, image: image, configuration: configuration, relevance: revelance)entries.append(entry)} else {let revelance = TimelineEntryRelevance(score: 10, duration: 0);let entry = SimpleEntry(date: entryDate, count: Int(intString)!, image: image, configuration: configuration, relevance: revelance)entries.append(entry)}}let timeline = Timeline(entries: entries, policy: .atEnd)completion(timeline)}
}

需要注意的是,我们给在第二分钟时要展现的数据分数 Relevance 分数设置为 2000,其它数据的分数设置为 10 分,此时运行 Widget 并等待到第二分钟,智能堆叠的 Widget 并不会一定翻转到我们的 Widget 上,但 Widget 上的数据是确确实实被更新了的,同时也说明了系统并不会一定认为当前数据比同一个 Widget 下的其它数据评分高,就一定为在智能堆叠上必须展示我们的 Widget,只是说在智能堆叠执行翻转时,我们的 Widget 会获得比其它 Widget 可能会获得更高的展示机会。

并且该评分也仅仅只是和当前 Widget 内的数据源做的对比。

程序员专栏 扫码关注填加客服 长按识别下方二维码进群

近期精彩内容推荐:  

 几句话,离职了

 中国男性的私密数据大赏,女生勿入!

 为什么很多人用“ji32k7au4a83”作密码?

 一个月薪 12000 的北京程序员的真实生活 !

在看点这里好文分享给更多人↓↓

从实际问题看 SwiftUI 和 Widget 编程相关推荐

  1. 【看动漫学编程】程序员在异世界生个娃 第2篇:外挂已准备就绪

    前言 作者文笔比较水,还请见谅. 以下内容还将使用视频动态漫画表现,剪辑完将会贴出链接. 小说剧情为剧情需要,过渡到知识点,部分篇幅可能没有技术知识点还望谅解. 由于没有经费支持,所以画出来的东西是我 ...

  2. 【看动漫学编程】程序员在异世界生个娃 第1篇:太极村

    前言 作者文笔比较水,还请见谅. 以下内容还将使用视频动态漫画表现,剪辑完将会贴出链接. 小说剧情为剧情需要,过渡到知识点,部分篇幅可能没有技术知识点还望谅解. 由于没有经费支持,所以画出来的东西是我 ...

  3. 初学者值得一看:什么是编程/C语言,编程学习建议,编程解疑与误区注意

    文章目录 :star: 初学者值得一看:什么是编程/C语言,编程学习建议,编程解疑与误区注意 1.什么是编程语言 2.什么是C语言与我的强烈建议 - 先学C,而不是c++,java等 3.C语言的发展 ...

  4. SwiftUI UISearchbar 如何编程控制cancle显示

    实战需求 SwiftUI UISearchbar 如何编程控制cancle显示 实战代码 searchController.isActive = false 推荐 基础文章推荐 <SwiftUI ...

  5. python 编程该看那些书籍_python编程入门书籍-零基础学习Python编程,这8本书必看!...

    作为一名程序员,你肯定知道Python语言,从2017年开始Python的热度就一路飙升,已经成为大量开发者推荐入门的编程语言和第二编程语言,而且Python还是人工智能的主要编程语言,受到大众的追捧 ...

  6. 看我用汉语编程(汉语Visual Basic编程软件发布)

    http://baike.baidu.com/view/14260.html?tp=0_01 Visual Basic(VB)是一种由微软公司开发的包含协助开发环境的事件驱动编程语言.从任何标准来说, ...

  7. SwiftUI之从前端视角看SwiftUI语言

    一.从 class 迈向 struct,从 class 迈向 function 可以将前端框架归纳为几个要素: 元件化: 响应式机制: 状态管理: 事件监听: 生命周期. 在写 SwiftUI 的时候 ...

  8. 高级程序员值得一看的33本编程书籍

    作为一名程序员,编程语言只是基础,只是工具,想实现从程序员到高级工程师的进步,需要花更多的精力在底层原理,算法,数据结构,编程思想上.推荐了33本高级程序员值得一看的书籍,注重底层知识,思想,用空的时 ...

  9. swiftui 跳转_酷!苹果推出 SwiftUI,提高编程效率

    (给iOS大全加星标,关注iOS动态) 原创整理:iOS大全(id: iOShub) 北京时间 6 月 4 日凌晨 WWDC 2019 大会上,苹果软件工程高级副总裁 Craig Federighi ...

最新文章

  1. input type右对齐与只读的
  2. 字符串-字符串反转(双指针)
  3. SKChoosePopView 一个HUD风格的可定制化选项弹窗的快速解决方案
  4. 创业第一站丨产品经理、海归转型成创业者有多难?
  5. C语言获取某个文件中一行内容中指定字符串后的值
  6. Android Gallery组件实现循环显示图像
  7. 父亲浮动,子代也会跟着走
  8. linux远程关机程序,木马中如何编程实现远程关机(VC版)
  9. IE下检测泄露的全局变量
  10. linux socket 开源库,linux c websocket开源库libwebsockets的编译和使用-Go语言中文社区...
  11. 伺服驱动器cn1引脚定义_伺服驱动器CN1引脚定义,和面板操作设置,跪求高手指点。...
  12. Arduino UNO驱动液晶屏TFT_LCD_ST7789v
  13. 学习笔记(14):程序员的数学:微积分-常用导数(一):最常用到的技巧
  14. Java多线程并发面试
  15. 微信小程序【小程序码传参,自定义埋点】
  16. 有限元方法之三角形元任意阶的Lagrange型形状函数
  17. tushare接口get_realtime_quotes报错:AssertionError: 33 columns passed, passed data had 34 columns
  18. 趣图:道高一尺,魔高一丈
  19. oracle是java代码块,Oracle中施行java代码
  20. 电脑C盘爆满了怎么办

热门文章

  1. 按下 Home 键后发生了什么事?
  2. 【调剂】中科院上海微系统与信息技术研究所2023年高校联培项目招收调剂生的通知...
  3. miui免root冻结,免root停用miui应用
  4. 小米误删userdata分区,userdata分区无法还原,安卓误删分区,且能进twrp,刷机卡米的情况,重新分区教程
  5. C语言实现一元多项式的加减运算
  6. 【MySQL】必知必会知识点
  7. 查看文章影响因子的插件_查询文献可实时显示影响因子与分区排名的2个强大浏览器插件...
  8. windows服务器直播推流
  9. Xposed插件开发---记录文件操作
  10. 软件设计师证书的作用有多大?