iOS界面操作指引的实现参考
Posted 追夢秋陽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS界面操作指引的实现参考相关的知识,希望对你有一定的参考价值。
当app 新增了功能为了更好地使用户熟悉和使用,需要使用操作指引来进行介绍和引导。效果参考图如下:
实现该功能有多个思路,本文中主要介绍的是将遮罩、镂空区域以及下一步、跳过等元素动态处理而非切整张图的形式来实现,优点是:减少包的大小以及使用代码动态适配定位需要指引的栏目,支持多界面滚动定位指引。缺点,如果界面很长,需要指引的栏目在可见区域之外,此时处理会稍微麻烦(详见文中代码注释,此处有很大改进空间)。
1、主体实现参考代码:
import UIKit
import YYImage
import RxSwift
import SnapKit
/// 操作指引视图
class DrmbbView: UIView
/// 操作事件集
private var arrActions:[DrmbbModel]
/// 描述图片的偏移量
private var oy:CGFloat
/// 页面高度过高,超出一个屏幕高度需要使用滚动来定位指示
private weak var contentScrollView:UIScrollView?
/// 跳过事件回调
var jumpActionBlock:(()->Void)?
/// 当前引导完成回调
var guideFinishBlock:(()->Void)?
//MARK: - override
init(frame:CGRect,
andActions _a:[DrmbbModel],
withScrollerView _scv:UIScrollView?,
andOffsetY _oy:CGFloat = 0)
self.arrActions = _a
self.oy = _oy
self.contentScrollView = _scv
super.init(frame: frame)
self.initView()
required init?(coder: NSCoder)
fatalError("init(coder:) has not been implemented")
override func updateConstraints()
if self.subviews.contains(self.btnJump)
self.btnJump.snp.makeConstraints make in
make.right.equalTo(-16)
make.width.equalTo(72)
make.height.equalTo(32)
make.top.equalTo(Setting.shareInstance.K_APP_SAFETY_NAV_HEIGHT + (44 - 32) * 0.5)
super.updateConstraints()
private func initView()
self.backgroundColor = .clear
self.addSubview(self.btnJump)
self.showNext()
setNeedsUpdateConstraints()
deinit
self.deInit()
print("\\(self.className()) 已销毁")
//MARK: - lazy load
private lazy var disposeBag = DisposeBag()
private lazy var showIndex:Int = 0
private lazy var cornerRadius:CGFloat = 0
/// 跳过
private lazy var btnJump:UIButton = [unowned self] in
let _btn = BaseView.createBtn(rect: .zero,
strTitle: "跳过",
titleColor: .white,
txtFont: UIFont.systemFont(ofSize: 14, weight: .regular),
image: nil,
backgroundColor: UIColor.init().colorFromHexInt(hex: 0xCF5F42),
borderColor: nil,
cornerRadius: 16,
isRadius: true,
backgroundImage: nil,
borderWidth: nil)
_btn.rx.tap.subscribe [weak self] (_:Event<Void>) in
guard let self = self else return
self.jumpActionBlock?()
self.deInit()
.disposed(by: self.disposeBag)
return _btn
()
/// 遮罩区域
private lazy var fillLayer:CAShapeLayer = [unowned self] in
let _layer = CAShapeLayer.init()
_layer.fillRule = .evenOdd
_layer.fillColor = UIColor.black.cgColor
_layer.opacity = 0.6
return _layer
()
/// 描述内容图片(下一步/知道了)
private lazy var btnContentImage:UIButton = [unowned self] in
let _btn = BaseView.createBtn(rect: .zero,
strTitle: nil,
titleColor: nil,
txtFont: nil,
image: nil,
backgroundColor: nil,
borderColor: nil,
cornerRadius: 0,
isRadius: false,
backgroundImage: nil,
borderWidth: nil)
_btn.showsTouchWhenHighlighted = false
_btn.rx.tap.subscribe [weak self] (_:Event<Void>) in
guard let self = self else return
self.showIndex = self.btnContentImage.tag + 1
self.showNext()
.disposed(by: self.disposeBag)
return _btn
()
//MARK: -
extension DrmbbView
/// 显示下一步
private func showNext()
if self.showIndex < self.arrActions.count
let _model = self.arrActions[self.showIndex]
//当前栏目的镂空区域
let _transparentRoundedRectPath = UIBezierPath.init(roundedRect: _model.contentFrame,
cornerRadius: _model.contentCornerRadius)
//[S]外层遮罩区域
let _bezierPath = UIBezierPath.init(rect: self.frame)
_bezierPath.append(_transparentRoundedRectPath)
_bezierPath.usesEvenOddFillRule = true
self.fillLayer.path = _bezierPath.cgPath
if self.layer.sublayers == nil || self.layer.sublayers?.contains(self.fillLayer) == false
self.layer.insertSublayer(self.fillLayer, at: 0)
//[E]
//[S]设置描述图片
if let _img:YYImage = YYImage.init(named: _model.descriptionImageName)
self.btnContentImage.setBackgroundImage(_img, for: .normal)
self.btnContentImage.tag = self.showIndex
if self.subviews.contains(self.btnContentImage) == false
self.addSubview(self.btnContentImage)
var _offsetTop = _model.contentFrame.origin.y + _model.contentFrame.size.height
if !_model.isFacedown
_offsetTop -= _model.contentFrame.size.height
_offsetTop -= _img.size.height
if !UIDevice.current.isiPhoneX()
_offsetTop -= Setting.shareInstance.K_APP_NAVIGATION_BAR_HEIGHT
else
_offsetTop -= self.oy
self.cornerRadius = _model.contentCornerRadius
self.scroller(ContentOffset: _model.contentFrame)
self.btnContentImage.snp.remakeConstraints make in
make.width.equalTo(_img.size.width)
make.height.equalTo(_img.size.height)
make.centerX.equalToSuperview()
make.top.equalTo(_offsetTop)
else
self.btnContentImage.removeFromSuperview()
self.cornerRadius = _model.contentCornerRadius
self.scroller(ContentOffset: _model.contentFrame)
//[E]
else
self.guideFinishBlock?()
self.deInit()
/// 销毁
private func deInit()
self.arrActions.removeAll()
self.jumpActionBlock = nil
self.guideFinishBlock = nil
self.contentScrollView = nil
self.fillLayer.removeAllAnimations()
self.fillLayer.removeFromSuperlayer()
self.subviews.forEach
$0.removeAllSubviews()
$0.removeFromSuperview()
$0.removeObserverBlocks()
$0.removeAssociatedValues()
self.removeFromSuperview()
self.removeObserverBlocks()
self.removeAssociatedValues()
/// 当前镂空区域,如果不在可见区域,则要重定位
private func scroller(ContentOffset _offset:CGRect)
let _oy = UIDevice.current.isSmallDevice() ? _offset.origin.y : _offset.size.height + _offset.origin.y
//[S] 显示的栏目不存在当前可见区域
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35)
var _vh:CGFloat? = self.contentScrollView?.frame.size.height
if #available(ios 12.0, *)
_vh = self.contentScrollView?.visibleSize.height
if _vh != nil && _vh! + Setting.shareInstance.K_APP_NAVIGATION_BAR_HEIGHT < _oy,
let _csize = self.contentScrollView?.contentSize,
let _vh = self.contentScrollView?.bounds.size.height
//[S] 滚到对应栏目
var _point = CGPoint.init(x: 0, y: _csize.height - _vh)
let _y = _point.y - _offset.size.height - (UIDevice.current.isiPhoneX() ? 0 : 286)
if _y > Setting.shareInstance.K_APP_HEIGHT - Setting.shareInstance.K_APP_TABBAR_HEIGHT
_point.y -= _offset.size.height - (UIDevice.current.isiPhoneX() ? 123 : 71)
self.contentScrollView?.setContentOffset(_point, animated: true)
//[E]
//[S] 重新定位描述图片位置
_point = CGPoint.init(x: _offset.size.width, y: _offset.size.height)
var _offsetTop = self.convert(_point, to: self.contentScrollView!)
if UIDevice.current.isiPhoneX(),
let _setPoint = self.contentScrollView?.convert(_point, to: self)
_offsetTop = _setPoint
var _newRect = _offset
_newRect.origin.y = _offsetTop.y
if self.subviews.contains(self.btnContentImage)
self.btnContentImage.snp.updateConstraints make in
make.top.equalTo(_newRect.origin.y + _newRect.size.height)
//[E]
//[S]更新镂空区域位置
let _transparentRoundedRectPath = UIBezierPath.init(roundedRect: _newRect,
cornerRadius: self.cornerRadius)
let _bezierPath = UIBezierPath.init(rect: self.frame)
_bezierPath.append(_transparentRoundedRectPath)
_bezierPath.usesEvenOddFillRule = true
self.fillLayer.path = _bezierPath.cgPath
//[E]
//[E]
模型文件:
import UIKit
/// 操作指引模型
struct DrmbbModel: Codable
/// 镂空区域位置
let contentFrame:CGRect
/// 镂空区域的圆角
let contentCornerRadius:CGFloat
/// 描述内容的图片
let descriptionImageName:String
/// 描述内容的图片朝向(true 朝下,false 朝上)
let isFacedown:Bool
///防止服务端下发字段多余当前字段,而无法匹配解析
struct DrmbbModel : Decodable
let contentFrame:CGRect
let contentCornerRadius:CGFloat
let descriptionImageName:String
let isFacedown:Bool
2、调用方法:
//MARK: 操作指引视图
private lazy var drmbbView:DrmbbView = [unowned self] in
let _rect:CGRect = CGRect.init(x: 0, y: 0,
width: Setting.shareInstance.K_APP_WIDTH,
height: Setting.shareInstance.K_APP_HEIGHT - Setting.shareInstance.K_APP_TABBAR_HEIGHT)
let _arrAction:[DrmbbModel] = [
DrmbbModel.init(contentFrame: CGRect.init(origin: CGPoint.init(x: 25, y: Setting.shareInstance.K_APP_NAVIGATION_BAR_HEIGHT + 90),size: CGSize.init(width: Setting.shareInstance.K_APP_WIDTH - 50, height: 71)),
contentCornerRadius: 12.5,
descriptionImageName: "trade_01.png",
isFacedown: true),
DrmbbModel.init(contentFrame: CGRect.init(origin: CGPoint.init(x: 25, y: Setting.shareInstance.K_APP_NAVIGATION_BAR_HEIGHT + 150),size: CGSize.init(width: Setting.shareInstance.K_APP_WIDTH - 50, height: 63)),
contentCornerRadius: 12.5,
descriptionImageName: "trade_02.png",
isFacedown: true),
DrmbbModel.init(contentFrame: CGRect.init(origin: CGPoint.init(x: 13, y: Setting.shareInstance.K_APP_NAVIGATION_BAR_HEIGHT + 225),size: CGSize.init(width: Setting.shareInstance.K_APP_WIDTH - 26, height: 154)),
contentCornerRadius: 12.5,
descriptionImageName: "trade_03.png",
isFacedown: true),
DrmbbModel.init(contentFrame: CGRect.init(origin: CGPoint.init(x: 13, y: Setting.shareInstance.K_APP_NAVIGATION_BAR_HEIGHT + 883),size: CGSize.init(width: Setting.shareInstance.K_APP_WIDTH - 26, height: 206)),
contentCornerRadius: 12.5,
descriptionImageName: "trade_04.png",
isFacedown: true),
]
let _v = DrmbbView.init(frame: _rect,
andActions: _arrAction,
withScrollerView: self.mainView.listCollectionView,
andOffsetY: 0)
_v.jumpActionBlock = [weak self] in
UserDefaults.standard.set(true, forKey: Key.shareInstance.trade_action_guide)
self?.drmbbView.removeFromSuperview()
self?.drmbbView.removeObserverBlocks()
self?.drmbbView.removeAssociatedValues()
_v.guideFinishBlock = [weak self] in
self?.drmbbView.jumpActionBlock?()
return _v
()
//在VC 的 viewDidLoad 中添加调用
self.view.addSubview(self.drmbbView)
经模拟器上不同设备及系统(iPhoneSE一代、iPhoneXs、iPhoneXR、iPhone6/7/8Plus)上测试,效果有出入,基本偏差在可接受范围内,有其他高见欢迎留言拍砖
以上是关于iOS界面操作指引的实现参考的主要内容,如果未能解决你的问题,请参考以下文章