Decorative image frame

autoreleasepool

trirocks blog

autoreleasepool

iOS15导航栏适配

iOS15导航栏适配

设置导航栏纯色/透明、解决ScrollView类上滑导航栏出现磨砂阴影的问题

在iOS 13中,苹果给导航栏的UINavigationBar增加了scrollEdgeAppearance属性。该属性应用在iOS 14及更早版本的大标题导航栏上,在iOS 15中scrollEdgeAppearance属性开始适用于所有的导航栏

官方解释:当关联的UIScrollView到达与导航条相邻的边缘(导航条的上边缘)时需要设置导航条的外观属性。如果没有设置,将默认使用修改后的standardAppearance。
scrollEdgeAppearance 与 standardAppearance 一样同属于 UINavigationBarAppearance 类型 父类是 UIBarAppearance
其中影响导航栏颜色、阴影涉及到以下属性:

1
2
3
4
5
6
7
8
@property (nonatomic, readwrite, copy, nullable) UINavigationBarAppearance *scrollEdgeAppearance UI_APPEARANCE_SELECTOR API_AVAILABLE(ios(13.0));

backgroundEffect 基于backgroundColor或backgroundImage的磨砂效果
backgroundColor 背景色,层级在backgroundImage之下
backgroundImage 背景图片
backgroundImageContentMode渲染backgroundImage时的内容模式。 默认是UIViewContentModeScaleToFill
shadowColor 阴影颜色(底部分割线)
shadowImage 阴影图片(可以使用图片的渲染模式:UIImageRenderingModeAlwaysOriginal 保持原色)

其中:

  1. 当shadowImage为nil时,直接使用此颜色为阴影色。如果此属性为nil或clearColor(需要显式设置),则不显示阴影。
  2. 如果shadowImage包含 template 图像,则使用该图像作为阴影并使用此属性中的值对其进行着色。如果此属性为nil或clearColor(需要显式设置),则不显示阴影。
  3. 如果shadowImage不包含 template 图像,则此属性无效。
兼容iOS15设置透明导航栏示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];]

if (@available(iOS 13.0, *)) {
UINavigationBarAppearance *scrollEdgeAppearance = [UINavigationBarAppearance new];
scrollEdgeAppearance.backgroundColor = [UIColor clearColor];
scrollEdgeAppearance.backgroundEffect = nil;
scrollEdgeAppearance.titleTextAttributes = @{NSForegroundColorAttributeName : [UIColor whiteColor],
NSFontAttributeName : [UIFont systemFontOfSize:18]};
self.navigationController.navigationBar.scrollEdgeAppearance = nil;
self.navigationController.navigationBar.standardAppearance = scrollEdgeAppearance;
}else {
self.navigationController.navigationBar.titleTextAttributes = @{NSForegroundColorAttributeName : [UIColor whiteColor],
NSFontAttributeName : [UIFont systemFontOfSize:18]};
[self.navigationBar setShadowImage:[UIImage new]];
UIImage *barImg = [[UIImage createImageWithColor:[UIColor whiteColor] size:CGSizeMake(SCREEN_WIDTH, 44.f)] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
[self.navigationController.navigationBar setBackgroundImage:barImg forBarMetrics:UIBarMetricsDefault];
}
//透明设置
self.navigationController.navigationBar.translucent = YES;
//navigationItem控件的颜色
self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
}

解决设置不透明导航栏,出现ScrollView类页面上滑,导航栏变成磨砂阴影效果

原因分析:
因为当设置scrollEdgeAppearance = nil,当前控制器如果使用有ScrollView类的控件,当ScrollView向上滚动时scrollEdgeAppearance会默认使用standardAppearance的属性效果。所以backgroundEffect和shadowColor属性需要显式设置为nil,以防止backgroundEffect、shadowColor有颜色值影响导航栏透明效果。

兼容iOS15设置不透明纯色导航栏示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)viewWillAppear:(BOOL)animated {
……

if (@available(iOS 13.0, *)) {
UINavigationBarAppearance *scrollEdgeAppearance = [UINavigationBarAppearance new];
scrollEdgeAppearance.backgroundColor = [UIColor whiteColor];
//基于backgroundColor或backgroundImage的磨砂效果
scrollEdgeAppearance.backgroundEffect = nil;
//阴影颜色(底部分割线),当shadowImage为nil时,直接使用此颜色为阴影色。如果此属性为nil或clearColor(需要显式设置),则不显示阴影。
scrollEdgeAppearance.shadowColor = nil;
……

}else {
// For Earlier version
……
}
//透明设置 包括原有的导航栏设置 需要保持
self.navigationController.navigationBar.translucent = NO;
//navigationItem控件的颜色
self.navigationController.navigationBar.tintColor = [UIColor blackColor];
}

参考:

  1. https://developer.apple.com/documentation/uikit/uinavigationbar/3198027-scrolledgeappearance?language=objc
  2. https://www.jianshu.com/p/23c1c4db3f2c

flask+vue 前端直传图片到s3

前一些天,遇到了一个需要使用直传的场景,现在我来把这个过程记录下来。

访问S3

在访问s3时,我使用的是两套环境,

本地

本地测试时用aws-cli创建的credential文件,本地上传时的鉴权,
可以自动读取。前端本地访问时,也可以直接使用这个方案,在环境配置完成,引入对应平台的SDK后,
在操作时,SDK都会自动完成鉴权操作,无需用户参与。

线上

在服务器上配置好相关角色,线上的前后端代码会自动读取配置,完成鉴权操作。例如在Python Flask中,使用
boto3时,无需关注如何初始化凭证信息,在创建时,直接使用即可:

1
s3_client = boto3.client('s3', region_name=region_name)

图片上传

服务端上传

如果通过服务端上传,不难实现,参考官方文档中给出的示例代码即可:

1
2
3
4
5
6
7
8
try:
response = s3_client.upload_file(file_name, bucket, object_name,
ExtraArgs={'ACL': 'public-read'}
)
except ClientError as e:
logging.error(e)
return False
return True

注释:file_name 其实就是文件存储的路径,bucket 是存储桶的名称,
object_name 指的是object_key,ACL指定了访问策略,这里是公开访问


前端直传

现在我们需要实现前端的直传,于是开始考虑实现两套方案

使用STS临时授权

想法是,使用sts模块的功能,依据当前环境中的角色权限生成sts的对象,此时sts对象的权限和本地就几乎一致,
后端凭此生成凭据后,返回给前端使用。
此时前端需要接入aws的sdk,使用对应的操作接口,即可完成上传,注意此时的凭证有效期是由后端指定的。

问题

在该方案执行过程中,发现有比较严重的安全隐患,返回给前端的凭据可以生成一个相当于有完整本地
权限的role,虽有时效性限制,但依旧不可行,故放弃了该方案。


方案示例

1
2
sts_client = boto3.client('sts', region_name=region_name)
res = sts_client.get_session_token() # res中为凭据

使用presigned URL

presigned URL可以提供一个预签名的URL,规定了上传的对象类型和请求类型,非常好的匹配了我们当前的需求。
我们依旧是通过boto3来获取:

1
2
3
4
5
6
7
8
9
10
11
s3_client = boto3.client('s3', region_name=region_name)
try:
response_url = s3_client.generate_presigned_url("put_object",
Params={'Bucket': Bucket,
'ACL': 'public-read',
'Key': object_key},
ExpiresIn=3600,
HttpMethod='PUT')
except ClientError as e:
logging.error(e)
return None

这段代码中,规定了执行的操作,上传的目的位置(存储桶),上传的文件的key和访问策略,该presigned URL的有效时长, 以及该URL可以使用的请求方法。
前端实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let config = {
headers: {
'x-amz-acl': 'public-read'
}
};
axios.put(presignedUrl, file, config)
.then(res => {
if (res.status == 200) {
# 上传成功
}
}).catch(
err => {
console.log(err)
}
)

其中,presignedUrl是后端返回的内容,file就是需要上传的file对象,需要注意的是请求header中无需额外指定content-type,不然会提示403错误, 即presignedURL的签名不匹配的问题。

这样前端直传应该就成功了。

对了,成功了是没有返回值的似乎。