最近在看小程序文档整理下简单的语法场景
1 | <!--index.wxml--> |
1 | data: { |
1 | <!--pages/temp.wxml--> |
页面传参 取参
1 | goNext(){ |
最近在看小程序文档整理下简单的语法场景
1 | <!--index.wxml--> |
1 | data: { |
1 | <!--pages/temp.wxml--> |
页面传参 取参
1 | goNext(){ |
在根目录新建http文件夹 新建 request.js文件 apiConfig.js文件
1 | // request.js文件 const request = { // 将接口参数和公共参数合并 buildParams(params) { // const uToken = wx.getStorageSync('uToken'); const baseParams = { // 用户token uToken: wx.getStorageSync('uToken') |
1 | /** |
1 | //app.js |
页面调用也分两种情况 一种是组件页面一种是正常的page页面
1 | // 正常页面调用接口 |
组件里面调用
注意这里得先在 Component 上面拿到app实例 const app = getApp()
1 | let opt = {}; // 接口入参 |
注意小程序还可以使用用混入逻辑(公共js部分)
1 | // 组件内调用 |
研究小程序的路上越走越远….坑很多 很不习惯刚刚开始
在utils文件夹下面新增api.js http.js文件
1 | // http.js |
1 | // api.js // 在这里面定义所有接口,一个文件管理所有接口,易于维护 // 引入刚刚封装好的http模块,import属于ES6的语法,微信开发者工具必须打开ES6转ES5选项 import { http } from './http'; // 每一个接口定义一个函数,然后暴露出去,供逻辑代码调用 // 接口请求的路由地址以及请求方法在此处传递 function postLoginApi(params) { |
1 | import http from '../../utils/api' |
主要有以下几种方式
url传参
事件通道 EventChannel
本地存储
应用全局变量
公共变量
方式和web中的方式一致。
<navigator url="/pages/index2/index2?name=海贼王">页面2</navigator>
或者
wx.navigateTo({
url: "/pages/index2/index2?name=海贼王"
})
onLoad: function (options) {
console.log(options);// { name : 海贼王}
},
需要注意的是,如果 index2 是tabbar页面,那么无法在onLoad中获取页面参数
如果一个页面由另一个页面通过 wx.navigateTo 打开,这两个页面间将建立一条数据通道:
这两个 EventChannel 对象间可以使用 emit 和 on 方法相互发送、监听事件。
<view>
来自于页面2 传递的数据: {{msg}}
</view>
Page({
data: {
msg: ""
},
onLoad: function () {
// 1 跳转到页面2
wx.navigateTo({
url: "/pages/index2/index2",
// 2 在成功的回调函数中获取事件通道对象
success: ({ eventChannel }) => {
// 3 监听自定义事件
eventChannel.on("data", (e) => {
// 4 获取页面2传递过来的数据 设置到 data中
this.setData({
msg: e.name
})
})
}
});
},
})
Page({
onLoad: function () {
// 被使用 wx.navigatorTo打开的页面获取获取到一个事件通道对象
const EventChannel = this.getOpenerEventChannel();
// 触发事件和传递参数到 页面1中
EventChannel.emit("data", { name: '海贼王' });
}
})
小程序中的本地存储用法类似web中,可以实现在整个应用中获取数据和存储数据
wx.setStorageSync('data', {name:'海贼王'});// 可以直接存任意类型的数据
wx.getStorageSync('data');// 获取
不同的页面都是处于一个共同的应用当中的,这个应用可以理解为 app.js
在这里可以定义公共数据
App({
myData: {
name: "悟空"
}
})
页面中可以通过 getApp 来获取
let app = getApp();
console.log(app.myData);
当然也可以直接修改
let app = getApp();
app.myData.name="八戒";
单独定义一个独立的js文件,把数据存储进去,即可
const data = {
name: "海贼王"
};
module.exports = data;
const data = require("../../common");
Page({
onLoad: function () {
console.log(data);
},
})
增加UISheetPresentationController,通过它可以控制 Modal 出来的 UIViewController 的显示大小,且可以通过拖拽手势在不同大小之间进行切换。只需要在跳转的目标 UIViewController 做如下处理:
if let presentationController = presentationController as? UISheetPresentationController {
// 显示时支持的尺寸
presentationController.detents = [.medium(), .large()]
// 显示一个指示器表示可以拖拽调整大小
presentationController.prefersGrabberVisible = true
}
UIButton支持更多配置。UIButton.Configuration是一个新的结构体,它指定按钮及其内容的外观和行为。它有许多与按钮外观和内容相关的属性,如cornerStyle、baseForegroundColor、baseBackgroundColor、buttonSize、title、image、subtitle、titlePadding、imagePadding、contentInsets、imagePlacement等。
// Plain
let plain = UIButton(configuration: .plain(), primaryAction: nil)
plain.setTitle("Plain", for: .normal)
// Gray
let gray = UIButton(configuration: .gray(), primaryAction: nil)
gray.setTitle("Gray", for: .normal)
// Tinted
let tinted = UIButton(configuration: .tinted(), primaryAction: nil)
tinted.setTitle("Tinted", for: .normal)
// Filled
let filled = UIButton(configuration: .filled(), primaryAction: nil)
filled.setTitle("Filled", for: .normal)
推出CLLocationButton用于一次性定位授权,该内容内置于CoreLocationUI模块,但如果需要获取定位的详细信息仍然需要借助于CoreLocation。
let locationButton = CLLocationButton()
// 文字
locationButton.label = .currentLocation
locationButton.fontSize = 20
// 图标
locationButton.icon = .arrowFilled
// 圆角
locationButton.cornerRadius = 10
// tint
locationButton.tintColor = UIColor.systemPink
// 背景色
locationButton.backgroundColor = UIColor.systemGreen
// 点击事件,应该在在其中发起定位请求
locationButton.addTarget(self, action: #selector(getCurrentLocation), for: .touchUpInside)
URLSession 推出支持 async/await 的 API,包括获取数据、上传与下载。
let session = URLSession.shared
// 加载数据
let (data, response) = try await session.data(from: url)
// 下载
let (localURL, _) = try await session.download(from: url)
// 上传
let (_, response) = try await session.upload(for: request, from: data)
系统图片支持多个层,支持多种渲染模式。
// hierarchicalColor:多层渲染,透明度不同
let config = UIImage.SymbolConfiguration(hierarchicalColor: .systemRed)
let image = UIImage(systemName: "square.stack.3d.down.right.fill", withConfiguration: config)
// paletteColors:多层渲染,设置不同风格
let config2 = UIImage.SymbolConfiguration(paletteColors: [.systemRed, .systemGreen, .systemBlue])
let image2 = UIImage(systemName: "person.3.sequence.fill", withConfiguration: config2)
UINavigationBar、UIToolbar 和 UITabBar 设置颜色,需要使用 UIBarAppearance APIs。
// UINavigationBar
let navigationBarAppearance = UINavigationBarAppearance()
navigationBarAppearance.backgroundColor = .red
navigationController?.navigationBar.scrollEdgeAppearance = navigationBarAppearance
navigationController?.navigationBar.standardAppearance = navigationBarAppearance
// UIToolbar
let toolBarAppearance = UIToolbarAppearance()
toolBarAppearance.backgroundColor = .blue
navigationController?.toolbar.scrollEdgeAppearance = toolBarAppearance
navigationController?.toolbar.standardAppearance = toolBarAppearance
// UITabBar
let tabBarAppearance = UITabBarAppearance()
toolBarAppearance.backgroundColor = .purple
tabBarController?.tabBar.scrollEdgeAppearance = tabBarAppearance
tabBarController?.tabBar.standardAppearance = tabBarAppearance
UITableView 新增了属性 sectionHeaderTopPadding,会给每一个section 的 header 增加一个默认高度。
tableView.sectionHeaderTopPadding = 0
UIImage 新增了几个调整尺寸的方法。
// preparingThumbnail
UIImage(named: "sv.png")?.preparingThumbnail(of: CGSize(width: 200, height: 100))
// prepareThumbnail,闭包中直接获取调整后的UIImage
UIImage(named: "sv.png")?.prepareThumbnail(of: CGSize(width: 200, height: 100)) { image in
// 需要回到主线程更新UI
}
// byPreparingThumbnail
await UIImage(named: "sv.png")?.byPreparingThumbnail(ofSize: CGSize(width: 100, height: 100))
某一天查看 App 的权限列表发现多了很多没声明过的权限,现在开发 App 都不可避免引入各种 SDK,很显然就是他们的锅,在背后干着不可告人的事情.. 怎么删除这些权限呢?
<uses-permission
android:name="android.permission.CHANGE_NETWORK_STATE"
tools:node="remove"
/>
这样 Android 处理 AndroidManifest.xml 文件时会自动删除这个权限。
有时我们在 debug 需要这个权限,但是在 release 正式版不想有这个权限。比如我在测试包的时候写了点测试功能,需要引入一个 android:name=”android.permission.SYSTEM_ALERT_WINDOW” 权限,但是正式环境并不需要。
一个做法是,将这个测试功能单独做成一个 lib 引入,正式版引用一个 lib-noop 版本,常见的第三方库如滴滴的多啦爱梦就是这么做的。
如果我们自己处理可以利用 gradle 直接对 xml 文件进行操作:
gradle 处理 Manifest 任务之后会有一个回调,我们在系统处理完之后再进行二次处理,通过 XmlParser 解析 xml 来修改文件。
以下是一个 删除 android.permission.SYSTEM_ALERT_WINDOW 的例子:
// 发布正式版时 将 manifest 中不必要的权限删除
android.applicationVariants.all { variant ->
if (variant.buildType.name == "release") {
def output = variant.outputs[0]
output.getProcessManifestProvider().get().doLast {
println("start process manifest")
def manifestFilePath = "${manifestOutputDirectory.asFile.get()}/AndroidManifest.xml"
def xmlParser = new XmlParser()
Node manifest = xmlParser.parse(file(manifestFilePath))
def android = new Namespace('http://schemas.android.com/apk/res/android', 'android')
def tools = new Namespace('http://schemas.android.com/tools', 'tools')
Node systemAlertWindow = manifest.get("uses-permission").find {
def name = it.attributes().get(android.name)
def remove = it.attributes().get(tools.remove)
name == "android.permission.SYSTEM_ALERT_WINDOW"
}
def remove = manifest.remove(systemAlertWindow)
// write xml to file
file(manifestFilePath).withPrintWriter { out ->
def printer = new XmlNodePrinter(out)
printer.preserveWhitespace = true
printer.print(manifest)
}
println("end process manifest, delete systemAlertWindow permission success? $remove")
}
}
}
之前早有耳闻 Google 为我们提供新的控件来替换老旧的 ViewPager 进而解决一些不好解决的bug问题,讲了一大堆,就是前因后果啥的…相信读者已经在“张鸿洋”大神、“郭霖”大神或者是其他Android 大佬的公众号那里看见了许许多多了,或许各位感觉很无聊了,笔者菜鸟,分析不了历史背景,也不是很懂源码,但是小菜鸟,可以带给位看官尝个鲜,教你怎么用,怎么上手哈,闲话不多说,我们步入正题。
不能关闭预加载
更新adapter不生效
我们在加载数据的时候,viewpager默认会帮我们预加载前后两个页面的数据,并且这2个view是不可见的。
由于viewpager对offscreenPageLimit做了限制,默认设置为1,因此页面的预加载不可避免。这也容易造成资源浪费。
一般使用viewpager与frament配合使用,利用fragment的setUserVisibleHint方法,来实现控制数据懒加载。而布局只能提前进入(预布局)。
ViewPager2的预加载与离屏加载在view层面有着本质的区别,离屏加载的view已经添加到parent上,而预加载只是准备了布局,并没有加载到parent上。
implementation 'androidx.viewpager2:viewpager2:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0' // ViewPager 2 需要使用 RecycleView 的 adapter
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/vp_rg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/rg_vp" />
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* CreateTime: 2021/10/30 21:32
* Author: iwen大大怪
*/
public class RgAdapter extends FragmentStateAdapter {
private List<Class> fragments;
public RgAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
if (fragments == null) {
fragments = new ArrayList<>();
}
}
public void addFragment(Fragment fragment) {
if (fragments != null) {
fragments.add(fragment.getClass());
}
}
@NonNull
@Override
public Fragment createFragment(int position) {
try {
return (Fragment) fragments.get(position).newInstance();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return null;
}
@Override
public int getItemCount() {
return fragments.size();
}
}
这里需要说的是 registerOnPageChangeCallback 方法 ,这个方法可以可以监听到 ViewPager 2 的界面变化,进而去操作其他的控件
使用过DiffUtil都知道它的好处,没有用使用过也没有关系,听我给你说说。
它可以通过计算判断两个列表之间的差异进行局部刷新,而我们平时使用的notifyDataSetChanged();是一个无脑刷新的操作,会刷新整个列表,对性能和视觉上并不是很友好。
使用差分进化算法计算更新的最小系数。
需要注意的是,如果你的列表数据量较大,建议在后台线程执行这个操作,DiffResult默认在主线程中执行。
如果启用了移动检测,则需要花费额外的o(N ^2)时间,其中N是已添加和已删除项目的总数。如果您的列表已经按相同的约束排序(例如,为帖子列表创建的时间戳),则可以禁用移动检测以提高性能。
开发中想知道一个协议被多少类实现了。有没有方法知道呢。
protocol Animal {
func speak()
}
class Cat:Animal {
func speak() {
print("meow")
}
}
class Dog: Animal {
func speak() {
print("An An!")
}
}
class Horse: Animal {
func speak() {
print("Hurrrr")
}
}
在网上找能找到这几个方法
上面两个方法,一个是知道有多少协议数组,一个是知道协议采用的协议数组。他们都没法知道一个协议被多少类实现。都没有达成目的。那就需要变通了。 反过来,找到这两个方法
利用这两个方法,那就可以这样,先获取所有的类,然后遍历每个类是否实现了目标协议。代码如下:
func getClassesConformingProtocol() -> {
let expectedClassCount = objc_getClassList(nil, 0)
let allClasses = UnsafeMutablePointer<AnyClass>.allocate(capacity: Int(expectedClassCount))
let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(allClasses)
let actualClassCount:Int32 = objc_getClassList(autoreleasingAllClasses, expectedClassCount)
for i in 0 ..< actualClassCount {
let currentClass = allClasses[Int(i)]
if class_conformsToProtocol(currentClass, Animal.Type) {
print(currentClass)
}
}
}
这里有个问题,在Swift中class_conformsToProtocol判断总是失败。
那就再变通,因为我们知道在swift中判断一个类是否遵循某个协议可以这样(aclass as? aprotocol)。代码修改如下:
func getClassesConformingProtocol() -> {
let expectedClassCount = objc_getClassList(nil, 0)
let allClasses = UnsafeMutablePointer<AnyClass>.allocate(capacity: Int(expectedClassCount))
let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(allClasses)
let actualClassCount:Int32 = objc_getClassList(autoreleasingAllClasses, expectedClassCount)
for i in 0 ..< actualClassCount {
let currentClass = allClasses[Int(i)]
if let cls = currentClass as? Animal.Type {
print(currentClass)
}
}
}
目标达成
当这部分代码被OC调用了,会出现这个错误。
*** NSForwarding: warning: object 0x10bec81d0 of class 'Object' does not implement methodSignatureForSelector: -- trouble ahead
*** NSForwarding: warning: object 0x10bec81d0 of class 'Object' does not implement doesNotRecognizeSelector: -- abort
解决方法也简单
func getClassesConformingProtocol() -> {
let expectedClassCount = objc_getClassList(nil, 0)
let allClasses = UnsafeMutablePointer<AnyClass>.allocate(capacity: Int(expectedClassCount))
let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(allClasses)
let actualClassCount:Int32 = objc_getClassList(autoreleasingAllClasses, expectedClassCount)
for i in 0 ..< actualClassCount {
let currentClass = allClasses[Int(i)]
if (class_getInstanceMethod(currentClass, NSSelectorFromString("methodSignatureForSelector:")) != nil),
(class_getInstanceMethod(currentClass, NSSelectorFromString("doesNotRecognizeSelector:")) != nil),
let cls = currentClass as? Animal.Type {
print(currentClass)
}
}
}
这个地方敲重点记下,目前Swift的数据,无非被这三大类定义:
struct(结构体)
enum(枚举)
class(类)
其中struct和enum可以说是同根同源,都在栈上,程序猿不用管理它的生命周期。
class在堆上,程序猿需要管理它的生命周期。
再者栈上的struct和enum都是值类型,堆上的class是引用类型。
不懂先记下来,这些都是需要实战才能完全理解的。
我们先看看Any到底是个啥东西:
protocol Any {}
Any其实是一个空协议,并且根据查阅资料看,三大类在创建的时候,都隐式的遵守了这个协议,你可以理解成为这样:
struct Person: Any {}
class Animal: Any {}
enum Type: Any {}
可以这么说,Any可以表示Swift语言构建的任何类型。
追加一句:包括函数类型,即闭包类型。像() -> Void也可以用Any表示
AnyObject其实就表示的就比较单一了,它表示的是类的类型。我摘选了一部分源码和注释,大家看看就理解了。
/// The protocol to which all classes implicitly conform.
///
/// You use `AnyObject` when you need the flexibility of an untyped object or
/// when you use bridged Objective-C methods and properties that return an
/// untyped result. `AnyObject` can be used as the concrete type for an
/// instance of any class, class type, or class-only protocol.
public typealias AnyObject
大家可能会好奇,那么我们经常这么写代码:
class AClass: NSObject {}
AnyObject和NSObject到底啥关系?
其实有两点我要解释:
写class的时候不需要都继承NSObject,Swift中的class会隐式继承一个特有的Swift.SwiftObject。建议IDE提示你需要添加NSObject的时候,再添加,一般这种时候都是需要实现代理。
AnyObject可以表示class,而class: NSObject怎么看都只是class的更细化分支,所以可以理解AnyObject包容了NSObject。
如果你的App在之前的iOS版本跑的挺好的,特别是那种浅色系风格的App,只是在深色模式下变得奇丑无比的话,有一种简单暴力的方法去适配深色模式。
那就是将你的App锁死在浅色模式或者黑色模式。这样无论外面设置怎么修改,App的配色岿然不动。
修改的方法也特别简单,在App中info.plist文件,添加如下代码就可以了。
锁死浅色:
<key>UIUserInterfaceStyle</key>
<string>Light</string>
锁死深色:
<key>UIUserInterfaceStyle</key>
<string>Dark</string>
如果你要比较细化的适配浅色和深色,请接着往下看。
抽取一个构造方法,可以传入浅色与深色的方法,生成一个UIColor,我的方法如下:
//MARK: - LightMode与DarkMode的颜色思路
extension UIColor {
/// 便利构造函数(配合cssHex函数使用 更好)
/// - Parameters:
/// - lightThemeColor: 明亮主题的颜色
/// - darkThemeColor: 黑暗主题的颜色
public convenience init(lightThemeColor: UIColor, darkThemeColor: UIColor? = nil) {
if #available(iOS 13.0, *) {
self.init { (traitCollection) -> UIColor in
switch traitCollection.userInterfaceStyle {
case .light:
return lightThemeColor
case .unspecified:
return lightThemeColor
case .dark:
return darkThemeColor ?? lightThemeColor
@unknown default:
fatalError()
}
}
} else {
self.init(cgColor: lightThemeColor.cgColor)
}
}
}
最好对于App类常用的颜色都进行UIColor的分类扩展编写。比如下面这种:
extension UIColor {
/// 文字颜色 light为黑 dark为白
static let playAndroidTitle = UIColor(lightThemeColor: .black, darkThemeColor: .white)
/// 背景颜色 light为白 dark为黑
static let playAndroidBg = UIColor(lightThemeColor: .white, darkThemeColor: .black)
}
使用的时候这样就可以了:
let label = UILabel()
label.textColor = .playAndroidTitle
另外我们可以通过iOS 11之后的新API与配置方法进行深色模式的适配:
先在Assets新建色彩资源:
2.设置好颜色配置。
3.调用
系统api如下
extension UIColor {
@available(iOS 11.0, *)
public /*not inherited*/ init?(named name: String) // load from main bundle
@available(iOS 11.0, *)
public /*not inherited*/ init?(named name: String, in bundle: Bundle?, compatibleWith traitCollection: UITraitCollection?)
}
编写分类:
extension UIColor {
/// UIColor的原始调用,硬编码不够安全,而且返回的还是一个可选类型,需要注意
/// 可以考虑使用R函数
static let theme = UIColor.colorNamed("mainTheme")
}
tag:
缺失模块。
1、请确保node版本大于6.2
2、在博客根目录(注意不是yilia根目录)执行以下命令:
npm i hexo-generator-json-content --save
3、在根目录_config.yml里添加配置:
jsonContent: meta: false pages: false posts: title: true date: true path: true text: false raw: false content: false slug: false updated: false comments: false link: false permalink: false excerpt: false categories: false tags: true