前言


上一篇我们总结的主要是VStack里面的东西,由他延伸到 @ViewBuilder, 接着我们上一篇总结的我们这篇内容主要说的是下面的几点,在这些东西说完后我准备解析一下苹果在SiwftUI文档中说道的比较好玩的一个东西,具体的我们后面在看。这篇我们还是说我们关于SwiftUI的东西,再提一下Demo代码我已经提交上Git了,目前Demo进度为一级页面基本上结束,地图点击大头针的添加也刚处理完,代码有需要的小伙伴可以去Git看看,项目地址

1、View之间的跳转(这里有个疑问需要帮忙!)

2、稍微复杂点View的布局思路和一些细节知识

3、SwiftUI循环轮播图

这次总结的首页的UI布局如下,我们下面一点点的解析:

界面跳转的问题


正常的界面跳转逻辑实现是比较简单的,我们先看看这个很简单的正常跳转,再说说我们的问题:

NavigationView{
VStack{
List{
/// 开关按钮
/// Toggle(isOn: $userData.showFavoritesOnly) {Text("Favorites only")}
ForEach(landmarkData) { landmark in if !self.userData.showFavoritesOnly || landmark.isFavorite { NavigationLink(destination:LandmarkDetail(landmark:landmark)
.environmentObject(self.userData),label:{
LandmarkRow(landmark: landmark)
})
}
}
}
.listStyle(PlainListStyle())
.navigationTitle("iPhone")
}
}

这是一个很普通的通过 NavigationView + NavigationLink 的界面跳转,在苹果给的 SwiftUI 的使用例子中就是这样写的,当然我们在正常的使用中这样写也没啥问题,那我们界面跳转的问题是什么呢?

如果你看了我们 Demo中的代码,你就知道我们是采用 TabView 嵌套 NavigationView 的形式,在这样的模式下似乎是存在问题的, 在 TabView+NavigationView 中你利用 NavigationLink 单击没法跳转,只有长按的时候才能跳转,这个问题抛出来,有懂得小伙伴希望能给我说一下,这个问题我也一直没有解决!具体的我们Demo中可以看看“我的”页面那个 List 的代码,问题就在那里。要理解这点的麻烦也给我说说,感谢!

首页布局


我们把首页这个布局给解析一下,大概分了下面几部分,我们再具体的说说:

我们看看最底层的代码先:

NavigationView{

       ScrollView(showsIndicators:false,content: {

                /// Banner视图
HomeBannerView()
.environmentObject(homeViewModel) /// 服务列表
HomeServiceCircleView().frame(
width: homeViewModel.homeServiceCircleWidth,
height: homeViewModel.homeServiceCircleHeight)
.environmentObject(homeViewModel)
.offset(y: -5) /// 滚动头条
HomeCircleNewsView().frame(
width: homeViewModel.homeNewsCircleWidth,
height: homeViewModel.homeNewsCircleHeight)
.environmentObject(homeViewModel) /// 四个按钮
HomeButtonView().frame(
width: homeViewModel.homeButtonViewWidth,
height: homeViewModel.homeButtonViewHeight)
.offset(y: -5) /// 服务列
HomeServiceListView().frame(
width: homeViewModel.homeServiceViewWidth,
height: homeViewModel.homeServiceViewHeight)
.environmentObject(homeViewModel) /// 最美的风景
HomeSnapshotView().environmentObject(homeViewModel)
}).navigationTitle(title)
}

这部分的代码没有啥特别需要说明的,都比较简单,可能是就是这个 environmentObject (我把它称为环境变量)这个是需要特别说明的一个变量,从名字上可以看出,这个修饰符是针对全局环境的。通过它我们可以避免在初始 View 时创建 ObservableObject, 而是从环境中获取 ObservableObject,像 @EnvironmentObject,@ObservedObject,@Binding 和 @States 这几个关键字还是需要需要我们特别理解的。下面这篇我们博客园的同行总结的还是很精辟的。传送门在这

下面是我们值得细说的一些点:

1、值得注意的 TabView + PageTabViewStyle

这是在iOS14中新出的一个值得我们注意的点,PageTabViewStyle 是14.0的新东西,但它的确能达到一个满意的翻页效果。和我们UIKit中的效果一样。具体的代码如下:

TabView(selection: $selection) {
/// 里面的具体内容,我们写了三页
ForEach(0..<3){
HomeServicePageView(pageIndex: $0)
.tag($0)
.environmentObject(homeViewModel)
}
}
/// PageTabViewStyle 14.0的新东西
.tabViewStyle(PageTabViewStyle())
.animation(.spring())

2、GeometryReader 它其实是有必要好好了解一下的。GeometryReader 的主要作用就是能够获取到父View建议的尺寸,这就是它的主要作用,要没有它我们面临的可能就是无休止的传值了,SwiftUI 既然是声明式的UI,按我的理解你就没有办法去获取某一个视图的父视图之类的。不然怎么体现声明这个点呢!

这个GeometryReader在前面第一期的时候我说过这个属性。

/// A proxy for access to the size and coordinate space (for anchor resolution)
/// of the container view.
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public struct GeometryProxy { /// The size of the container view.
public var size: CGSize { get } /// Resolves the value of `anchor` to the container view.
public subscript<T>(anchor: Anchor<T>) -> T { get } /// The safe area inset of the container view.
public var safeAreaInsets: EdgeInsets { get } /// Returns the container view's bounds rectangle, converted to a defined
/// coordinate space.
public func frame(in coordinateSpace: CoordinateSpace) -> CGRect
}

* size 比较直观,就是返回父View建议的尺寸

** subscript 可以让我们获取.leading,.top等等类似这样的数据

*** safeAreaInsets 可以获取安全区域的Insets

**** frame(in:) 要求传入一个CoordinateSpace类型的参数,也就是坐标空间,可以是.local.global 或者  .named(),其中 .named()可以自定义坐标空间。

有一个还得说明一下,GeometryReader 改变了它显示内容的方式。在 iOS 13.5 中,内容放置方式为 .center。在 iOS 14.0 中则为:.topLeading。

3、再提一点关于上面说的滚动视图,在UIKit中我们可以用UICollectionView搞定一切,但是在SwiftUI中没有这个控件,我建议采用的方式是 ScrollView + HStack + VStack  的方式去实现,很多同行有说目前来看SwiftUI的List在数据量大的情况下性能不是特别好,采用ScrollView是个不错的方式,而且也很容易构建出来,并不是说每一个Item的位置都需要你去计算,所以没啥可以担心的。

除了这个List,还要一个From我们也可以了解下,他们俩肉眼可见的区别 在选中这个点上的区别。

循环轮播实现


总结一下循环轮播怎么实现,采用的方案就是 HStack + Gesture + Timer 的方式,这三者就能实现一个自动循环滚动或者手动滚动的轮播。然后缩放的方式还是比较简单的,我们采用改变下Image的frame的方式。

HStack 这没啥可以具体说的,可以看代码,注释比较多,就不在这里累赘了。

Gesture 这个我们可以说说,它就是我们具体手势的父类,像我们的单击手势和我们这里用到的拖拽手势一样。具体的我们会看下面的代码,他们的区别就是像拖拽我们可以监控它的改变状态,点击或者双击、长按等我们可以添加事件等等。下面是拖拽的代码:

/// 定义拖拽手势
private var dragGesture: some Gesture{ DragGesture()
/// 拖动改变
.onChanged { isAnimation = true
dragOffset = $0.translation.width
}
/// 结束
.onEnded { dragOffset = .zero
/// 拖动右滑,偏移量增加,显示 index 减少
if $0.translation.width > 50{
currentIndex -= 1
}
/// 拖动左滑,偏移量减少,显示 index 增加
if $0.translation.width < -50{
currentIndex += 1
}
/// 防止越界
currentIndex = max(min(currentIndex, homeViewModel.homeBannerCount() - 1), 0)
}
}

再看看Timer,SwiftUI区别于我们UIKit的创建方式,SwiftUI对它进行了简化,具体的创建如下:

/// SwiftUI对定时器的简化,可以进去看看具体参数的定义
private let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()

它不像我们UIKit的需要我们绑定事件,那它的事件是怎么处理的呢?看看下面的代码:

/// 对定时器的监听
.onReceive(timer, perform: { _ in
currentIndex += 1
}

它的事件就是通过 onReceive 监听处理的,所有通过 publish 创建的都是可以通过 onReceive 监听的。那还有啥事通过 publish 创建的呢?我所用到的就是 NotificationCenter。

这样基本上循环轮播的实现我们基本上都说清楚了,具体里面的一些实现细节代码注释写的清清楚楚,还是仔细看看代码结合里面的注释来看,难度不是很大。首页顶部自动循环轮播的代码实现如下,代码里有些注释还是比较重要的,注意看注释:

struct HomeBannerView: View {

    @EnvironmentObject var homeViewModel: HomeViewModel

    /// SwiftUI 对定时器的简化,可以进去看看具体参数的定义
private let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect() /// 拖拽的偏移量
@State var dragOffset: CGFloat = .zero
/// 当前显示的位置索引,
/// 这是实际数据中的1就是数据没有被处理之前的0位置的图片
/// 所以这里默认从1开始
@State var currentIndex: Int = 1
/// 是否需要动画
@State var isAnimation: Bool = true let spacing: CGFloat = 10 var body: some View { /// 单个子视图偏移量 = 单个视图宽度 + 视图的间距
let currentOffset = CGFloat(currentIndex) * (homeViewModel.homeBannerWidth + spacing)
/// GeometryReader 改变了它显示内容的方式。在 iOS 13.5 中,内容放置方式为 .center。在 iOS 14.0 中则为:.topLeading
GeometryReader(content: { geometry in HStack(spacing: spacing){ ForEach(0..<homeViewModel.homeBannerCount()){ /*
如果想自定义Image大小,可以添加frame
clipped()相当于UIKit里的clipsToBounds,
与aspectRatio(contentMode: .fill)搭配使用。 注意:frame 要放在resizable后面,否则报错,
如果需求裁剪,需要放在aspectRatio后面,
clipped()前面,否则frame失效 */ Image(homeViewModel.bannerImage($0)).resizable()
/// 自己尝试一下.fill和.fit
.aspectRatio(contentMode: .fill)
.frame(width: geometry.size.width,
height: $0 == currentIndex ? geometry.size.height:geometry.size.height*0.8 )
.clipped() /// 裁减
.cornerRadius(10)
}
}.frame(width:geometry.size.width,
height:geometry.size.height,alignment:.leading)
.offset(x: dragOffset - currentOffset)
.gesture(dragGesture)
/// 绑定是否需要动画
.animation(isAnimation ?.spring():.none)
/// 监听当前索引的变化,最开始初始化为0是不监听的,
.onChange(of: currentIndex, perform: { value in isAnimation = true
/// 第一张的时候
if value == 0 { isAnimation.toggle()
currentIndex = homeViewModel.homeBannerCount() - 2
/// 最后一张的时候currentIndex设置为1关闭动画
}else if value == homeViewModel.homeBannerCount() - 1 { isAnimation.toggle()
currentIndex = 1
}
})
/// 对定时器的监听
.onReceive(timer, perform: { _ in
currentIndex += 1
})
}).frame(width: homeViewModel.homeBannerWidth,
height: homeViewModel.homeBannerHeight)
}
} // MARK: -
extension HomeBannerView{ /// 定义拖拽手势
private var dragGesture: some Gesture{ DragGesture()
/// 拖动改变
.onChanged { isAnimation = true
dragOffset = $0.translation.width
}
/// 结束
.onEnded { dragOffset = .zero
/// 拖动右滑,偏移量增加,显示 index 减少
if $0.translation.width > 50{
currentIndex -= 1
}
/// 拖动左滑,偏移量减少,显示 index 增加
if $0.translation.width < -50{
currentIndex += 1
}
/// 防止越界
currentIndex = max(min(currentIndex, homeViewModel.homeBannerCount() - 1), 0)
}
}
}

参考文章:

项目地址

SwiftUI之GeometryReader

理解SwiftUI关键字 State  Binding ObservesOgiect EnvironmentObje

SwiftUI 自定义实现旋转木马轮播效果

解析SwiftUI布局细节(二)循环轮播+复杂布局的更多相关文章

  1. Android无限循环轮播广告位Banner

     Android无限循环轮播广告位Banner 现在一些app通常会在头部放一个广告位,底部放置一行小圆圈指示器,指示广告位当前的页码,轮播展示一些图片,这些图片来自于网络.这个广告位banner ...

  2. js 实现图片间隔循环轮播以及没有间隔的循环轮播

    链接地址:http://blog.sina.com.cn/s/blog_75cf5f32010199dn.html 最近做了个图片循环轮播的功能.就是几张图片不断的循环滚动显示. 感觉这个方法不错所以 ...

  3. 超级详细 一听就会:利用JavaScript jQuery实现图片无限循环轮播(不借助于轮播插件)

    前言 作为一个前端工程师,无论公司是什么行业,无论你做什么端,基本都会遇到一个避不开的动画效果:循环轮播.做轮播并不难,市场上的轮播插件有很多,其中比较著名的是swiper,使用也非常简单.但轮播插件 ...

  4. swiper在vue项目中的循环轮播bug以及点击事件

    一般的,如果是静态数据(本地数据),可以直接在mounted生命周期中初始化,循环轮播.自动播放都比较正常. 但是,如果是动态从后台获取数据的话,采用上述方法会发现,轮播图无法自动播放,也无法拖拽. ...

  5. 解决ajax异步请求数据后swiper不能循环轮播(loop失效)问题、滑动后不能轮播的问题。

    问题描述: 1.我使用axios异步请求后台的图片进行渲染后不能实现循环轮播,也就是loop失效,但是静态写死的情况下不会出现这种问题. 2. 分析: swiper的机制是:初始化的时候将swiper ...

  6. 非常简单的方法实现ViewPager自动循环轮播

    非常简单的方法实现ViewPager自动循环轮播,见红色代码部分,其它的代码可以忽略不看. 简洁高效是我解决问题的首要出发点. package com.shuivy.happylendandreadb ...

  7. 利用JavaScript jQuery实现图片无限循环轮播(不借助于轮播插件)-----转载

    前言 作为一个前端工程师,无论公司是什么行业,无论你做什么端,基本都会遇到一个避不开的动画效果:循环轮播.做轮播并不难,市场上的轮播插件有很多,其中比较著名的是swiper,使用也非常简单.但轮播插件 ...

  8. Vue编写轮播组件引入better-scroll插件无法正常循环轮播

    临近过年还是发个博客表示一下自己的存在感,这段时间公司突然说想搞小程序,想到这无比巨大的坑就只能掩面而泣,于是乎这段时间在学习小程序开发.关于标题所说的是有老铁问的,我也跟着网上的代码码了一遍然后发现 ...

  9. 利用jQuery实现图片无限循环轮播(不借助于轮播插件)

    原来我主要是用Bootstrap框架或者swiper插件实现轮播图的功能,而这次是用jQuery来实现图片无限循环轮播! 用到的技术有:html.css.JavaScript(少).jQuery(主要 ...

随机推荐

  1. Python中字符串使用单引号、双引号标识和三引号标识,什么是三引号?什么情况下用哪种标识?

    一.三引号是指三个单引号或者三个双引号: 二.Python中字符串如果以单引号.双引号标识和三引号标识开头,则字符串结尾也必须是对应的标识,不能变更: 三.三者的异同: 1.三者都是字符串,大部分情况 ...

  2. 第二十六章、containers容器类部件QToolBox工具箱详解

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.概述 容器部件就是可以在部件内放置其他部件的部件,在Qt Designer中可以使用的容器部件有 ...

  3. PyQt(Python+Qt)学习随笔:Qt Designer中QAbstractButton派生按钮部件的text属性

    text属性保存按钮上显示的文字,如果按钮未设置文字则为空字符串.如果文字中包含有与符号('&'),则该按钮会自动设置一个快捷键,快捷键就是'&'后第一个字符,显示时会在该字符下加下划 ...

  4. PyQt(Python+Qt)学习随笔:窗口对象尺寸调整相关的函数resize、showMaximized、showNormal、showMinimized

    resize(width,height) resize可以直接调整窗口的尺寸,调整效果类似于鼠标直接拉伸或缩小窗口,但窗口大小的最大值.最小值受窗口的sizePolicy.sizeHint.minim ...

  5. NET CORE通过NodeService调用js

    在 .NET Framework 时,我们可以通过V8.NET等组件来运行 JavaScript,不过目前我看了好几个开源组件包括V8.NET都还不支持 .NET Core ,我们如何在 .NET C ...

  6. CF1147F Zigzag Game & 稳定婚姻问题学习笔记

    CF1147F Zigzag Game 这题太神仙了,不得不记录一下. 我网络流做不动了,DS做不动了,DP做不动了,特别自闭.于是博弈论之神(就是随手切3500博弈的那种) \(\color{bla ...

  7. Python之Windows服务

    1.首先要安装pywin32-220.win-amd64-py2.7.exe 2. SvcDoRun:服务启动的时候会执行的方法 SvcStop:服务停止的时候会执行的方法 # coding=utf- ...

  8. 上传python代码到pypi

    上传python代码到pypi 去pypi官网注册账号 在项目中添加setup.py # coding = utf-8 from setuptools import setup, find_packa ...

  9. 侧边栏js样式代码

    <!-- menu html --> <div class="container"> <div class="menu-wrap optis ...

  10. html+css一些简单案例:爱心点击,盒子模型,2d动画

    canvas绘制爱心 效果预览 上代码 <!doctype html> <html> <head> <title>HTML5 Canvas爱心飘动动画特 ...