Commit 0b841956 authored by David黄金龙's avatar David黄金龙

文件移动

parent 277f5d93
//
// BsBaseViewController.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/10/21.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
//import Kingfisher
class YHBaseViewController: UIViewController {
lazy var noDataView:YHNoDataTipView = {
let temp = YHNoDataTipView()
temp.frame = view.bounds
temp.isHidden = true
let tap = UITapGestureRecognizer(target: self, action: #selector(tapNodataViewEvent(tap:)))
temp.addGestureRecognizer(tap)
return temp
}()
override func viewDidLoad() {
super.viewDidLoad()
self.bs_setupLeftButton()
//0
setupNavigationAttributed()
//1、
view.addSubview(noDataView)
view.backgroundColor = .white
}
deinit {
#if DEBUG
print(#function)
#endif
}
}
// MARK: - 私有操作方法
extension YHBaseViewController {
@objc func tapNodataViewEvent(tap: UITapGestureRecognizer) -> Void {
print("do nothing~~~ 子类根据具体情况来实现\(#function)")
}
func openFullScreenBackGes(_ openFlag : Bool) {
if self.navigationController is YHNavigationController {
self.navigationItem.hidesBackButton = !openFlag
}
}
///设置导航条属性
func setupNavigationAttributed(_ color: UIColor? = nil) {
if let color = color {
self.navigationController?.navigationBar.setBackgroundImage(UIImage.from(color: color), for: .default) //设置导航栏背景色
self.navigationController?.navigationBar.barTintColor = color
} else {
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) //设置导航栏背景透明色
self.navigationController?.navigationBar.barTintColor = nil
}
self.navigationController?.navigationBar.shadowImage = UIImage() //隐藏下划线
}
}
//
// CustomNavigationController.swift
// DYZB
//
// Created by 1 on 16/10/14.
// Copyright © 2016年 小码哥. All rights reserved.
//
import UIKit
public protocol BsNavigationControllerDelegate : NSObject {
func canBack() -> Bool
}
class YHNavigationController: UINavigationController {
weak var myDelegate : BsNavigationControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.interactivePopGestureRecognizer?.delegate = self
}
// override func viewWillLayoutSubviews() {
// super.viewWillLayoutSubviews()
// if #available(iOS 14.0, *){
// self.navigationBar.topItem?.backButtonDisplayMode = .minimal
// }
// }
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
// 隐藏要push的控制器的tabbar
if animated == true {
viewController.hidesBottomBarWhenPushed = true
}
super.pushViewController(viewController, animated: animated)
}
}
// MARK: - UIGestureRecognizerDelegate, UINavigationBarDelegate
extension YHNavigationController : UIGestureRecognizerDelegate,UINavigationBarDelegate {
//这个方法是在手势将要激活前调用:返回YES允许右滑手势的激活,返回NO不允许右滑手势的激活
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
print(#function)
if let returnValue = myDelegate?.canBack(){
return returnValue
} else {
#if DEBUG
print(#function)
print("myDelegate == nil 或者其他")
#endif
}
return true
}
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
if let returnValue = myDelegate?.canBack(){
return returnValue
}
return true
}
}
//
// BsTabBarViewController.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/12/4.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
import ESTabBarController_swift
class YHTabBarViewController: ESTabBarController {
override func viewDidLoad() {
super.viewDidLoad()
self.tabBar.shadowImage = UIImage()
self.tabBar.backgroundImage = UIImage()
// NotificationCenter.default.addObserver(self, selector: #selector(hideTabBar), name: BsConstant.BsNotification.tabBarHideNotification, object: nil)
//
// NotificationCenter.default.addObserver(self, selector: #selector(showTabBar), name: BsConstant.BsNotification.tabBarShowNotification, object: nil)
}
}
extension YHTabBarViewController {
@objc func hideTabBar() -> Void {
self.tabBar.isHidden = true
}
@objc func showTabBar() -> Void {
self.tabBar.isHidden = false
}
}
//
// BsWebViewController.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/11/11.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
import WebKit
extension WKWebView {
open override var safeAreaInsets: UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
}
class YHWebViewController: YHBaseViewController {
public var isShowNavigation = true
public var isPresent = false
public var isShowShared = true
// MARK: - 懒加载方法
lazy var kvoHelper = BsKVOHelper()
var urlString : String?
var userAgent:String {
get {
if let content = WKWebView().value(forKey: "userAgent") as? String {
return content
} else {
return ""
}
}
}
public func joinNav() -> UINavigationController {
self.isPresent = true
self.isShowShared = false
let nav = YHNavigationController(rootViewController: self)
nav.modalPresentationStyle = .fullScreen
return nav
}
var isStatusBarHidden = false{
didSet{
if isStatusBarHidden != oldValue, isViewLoaded{
setNeedsStatusBarAppearanceUpdate()
}
}
}
var statusBarStyle = UIStatusBarStyle.lightContent{
didSet{
if statusBarStyle != oldValue, isViewLoaded{
setNeedsStatusBarAppearanceUpdate()
}
}
}
override var prefersStatusBarHidden: Bool{
return isStatusBarHidden
}
override var preferredStatusBarStyle: UIStatusBarStyle{
return statusBarStyle
}
var webView: WKWebView!
lazy var progressView: UIProgressView = {
let temp = UIProgressView()
temp.tintColor = .mainColor
temp.trackTintColor = .white
return temp
}()
//是否加载了js方法
var hasAddJsMethodFlag : Bool = false
// MARK: - 生命周期方法
deinit {
if isViewLoaded {
kvoHelper.remove(observer: self, atObject: webView, forKeyPath: "estimatedProgress")
kvoHelper.remove(observer: self, atObject: webView, forKeyPath: "title")
}
}
override func viewDidLoad() {
super.viewDidLoad()
setupSubview()
addLayout()
registeGlobalUserAgent()
(self.navigationController as? YHNavigationController)?.myDelegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if isShowNavigation == false {
self.navigationController?.navigationBar.isHidden = true
}
if hasAddJsMethodFlag == false {
webView.configuration.userContentController.add(self, name: "methodForJsInvoke")
hasAddJsMethodFlag = true
}
}
func deinitAction() {
if isShowNavigation == false {
self.navigationController?.navigationBar.isHidden = false
}
//不执行会导致循环引用
webView?.configuration.userContentController.removeScriptMessageHandler(forName: "methodForJsInvoke")
//webView.configuration.userContentController.removeAllContentRuleLists()
(self.navigationController as? YHNavigationController)?.myDelegate = nil
}
}
// MARK: - WKNavigationDelegate
extension YHWebViewController:WKUIDelegate,WKNavigationDelegate {
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
// showEmptyView(showFlag: true)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// showEmptyView(showFlag: false)
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
// showEmptyView(showFlag: false)
}
// func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
//// var host: String? = nil
//// if let ipHost = webView.url?.host,let realHost = hostMap[ipHost]{
//// host = realHost
//// }
//// sessionTaskDelegate.handlerChallenge(challenge, host: host,
//// completionHandler: completionHandler)
// }
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
// if let ipHost = navigationAction.request.url?.host,
// let realHost = hostMap[ipHost]{
// var copyRequest = (navigationAction.request as NSURLRequest).httpdns_getPostIncludeBody()
// if copyRequest.allHTTPHeaderFields?["Host"] == nil{
// copyRequest.addValue(realHost, forHTTPHeaderField: "Host")
// webView.load(copyRequest)
// decisionHandler(.cancel)
// return
// }
// }
//
// if let requestUrl = navigationAction.request.url?.absoluteString,
// requestUrl.hasPrefix(RyMTAHybRequest.prefix) {
// print("mta-ry: \(navigationAction.request.url?.absoluteString ?? "null")")
// var requestUrlRemovingPercentEncoding = requestUrl.removingPercentEncoding ?? requestUrl
// requestUrlRemovingPercentEncoding = requestUrlRemovingPercentEncoding.substring(from: RyMTAHybRequest.prefix.count)
// if let obj = RyMTAHybRequest(JSONString: requestUrlRemovingPercentEncoding), obj.trackEvent() {
// decisionHandler(.cancel)
// return
// }
// if MTAHybrid.handle(navigationAction, from: webView){
// decisionHandler(.cancel)
// return
// }
// }
decisionHandler(.allow)
}
}
extension YHWebViewController {
func hideProgress() {
progressView.isHidden = true
}
func showProgress() {
progressView.isHidden = false
}
func goBack(){
if webView.window != nil,webView.canGoBack{
webView.goBack()
}else if navigationController?.viewControllers.count ?? 0 > 1{
navigationController?.popViewController(animated: true)
deinitAction()
}else{
dismiss(animated: true, completion: nil)
deinitAction()
}
}
func setupSubview(){
view.backgroundColor = .white
if let urlString = urlString {
webView = BsWebViewPreloadManager.share.getWebView(urlString: urlString)
webView.frame = UIScreen.main.bounds
webView.scrollView.keyboardDismissMode = .onDrag
webView.backgroundColor = .backgroundColor
webView.isOpaque = false
webView.uiDelegate = self
webView.navigationDelegate = self
webView.allowsLinkPreview = false
webView.scrollView.bounces = false
webView.scrollView.showsVerticalScrollIndicator = false
webView.scrollView.showsHorizontalScrollIndicator = false
kvoHelper.add(observer: self, toObject: webView, forKeyPath: "estimatedProgress", options: .new, context: nil)
kvoHelper.add(observer: self, toObject: webView, forKeyPath: "title", options: .new, context: nil)
view.addSubview(webView)
} else {
BsHUD.flash(message: "urlString 为空")
//#if DEBUG
// if let url = Bundle.main.url(forResource: "WKWebView-WKScriptMessageHandler", withExtension: "html") {
// let request = URLRequest(url: url)
// webView = BsWebViewPreloadManager.share.onlyCreateWebView()
// webView.load(request)
// webView.navigationDelegate = self;
// view.addSubview(webView)
//
// webView.scrollView.contentInsetAdjustmentBehavior = .never
//
// let loginBtnItem = UIBarButtonItem(title: "测试", style: .plain, target: self, action: #selector(onClickTestItem))
// self.navigationItem.rightBarButtonItem = loginBtnItem
// }
//#endif
}
view.addSubview(progressView)
view.backgroundColor = .white
#if DEBUG
let testBtn = UIButton(type: .custom)
testBtn.setTitle("点我呀,豪哥", for: .normal)
testBtn.setTitleColor(.mainColor, for: .normal)
testBtn.backgroundColor = .purple
view.addSubview(testBtn)
testBtn.addTarget(self, action: #selector(onClickTestItem), for: .touchUpInside)
testBtn.snp.makeConstraints { make in
make.top.equalTo(200)
make.right.equalTo(-16)
make.height.equalTo(44)
make.width.equalTo(150)
}
// TODO: - for test hjl 正式发布版本前需要屏蔽下面2个代码
webView.backgroundColor = .purple
#endif
if isPresent == true {
self.bs_setupLeftButton(image: UIImage(named: "close_icon"))
}
if isShowShared == true {
self.bs_setupRightButton(image: UIImage(named: "nav_share_icon"))
}
}
// MARK: - event response 事件响应包括手势和按钮等
//事件响应,包含手势、按钮、通知等等事件的处理
@objc override func clickedNavRight() {
// //1.判断是否登录
// if BsLoginManager.checkLoginStatus(andLogin: true, invokeVc: self) == true {
// BsHUD.flash(message: "分享功能还需要讨论")
// }
}
@objc private func onClickTestItem() -> Void {
let dic = ["type":"appToken","param":["token" : "豪哥你真帅~~~","key2":"value2"]] as [String : Any]
let json = BSJsonUtil.dictionaryToJson(dic as NSDictionary)
let jsString = "methodForAppInvoke('\(json)')"
webView.evaluateJavaScript(jsString) { (result : Any?, error : Error?) in
print("result")
print(result as Any)
print("error")
print(error as Any)
}
self.webView.backgroundColor = .random
}
private func appPassTokenToH5() -> Void {
// let dic = ["type":"appToken","param":["token" : BsLoginManager.shared.loginInfoModel?.access_token ?? ""]] as [String : Any]
// let json = BSJsonUtil.dictionaryToJson(dic as NSDictionary)
// let jsString = "methodForAppInvoke('\(json)')"
// webView.evaluateJavaScript(jsString) { (result : Any?, error : Error?) in
// print("result")
// print(result as Any)
//
// print("error")
// print(error as Any)
// }
}
func addLayout(){
webView.snp.makeConstraints { (make) in
if isShowNavigation == true {
make.top.equalToSuperview().offset(UIDevice.bs_navigationFullHeight())
} else {
make.top.equalToSuperview()
}
make.left.equalToSuperview()
make.right.equalToSuperview()
make.bottom.equalToSuperview()
}
if isShowNavigation == true {
progressView.snp.makeConstraints { (make) in
make.top.equalTo(webView)
make.left.equalToSuperview()
make.right.equalToSuperview()
make.height.equalTo(2)
}
}
}
private func loadURL(url : String? = nil){
print("load url: \(url ?? "")")
if let nsURL = URL(string: url){
let request = URLRequest(url: nsURL)
webView.load(request)
}
}
func registeGlobalUserAgent() {
assert(!self.userAgent.isEmpty)
let newUserAgent = self.userAgent
let version = UIDevice.appVersion() + "." + UIDevice.appBuild()
/*
env 环境: dev 开发、trial 体验、prod 正式
token: 密钥
immerse: true / false APP是否沉浸式
scenes: app / mini / web 进入场景 (为后期做数据埋点上报使用)
*/
let token = ""//BsLoginManager.shared.loginInfoModel?.access_token ?? ""
let uid = ""//BsLoginManager.shared.loginInfoModel?.user_id ?? 0
let phone_num = ""//BsLoginManager.shared.loginInfoModel?.phone_num ?? ""
var dict = ["version":"\(version)",
"uid":"\(uid)",
"token":"\(token)",
"phone_num":"\(phone_num)",
"scenes":"iOS",
"system":newUserAgent,
"statusbar":["width":UIDevice.kScreenW,"height":UIDevice.bs_statusBarHeight()]
] as [String : Any]
#if DEBUG
dict["appName"] = "优眠健康-Dev-iOS"
dict["env"] = "dev"
#else
dict["appName"] = "优眠健康-Release-iOS"
dict["env"] = "prod"
#endif
dict["sys-wkweb"] = newUserAgent
let jsonString = dict.jsonString()
webView.customUserAgent = jsonString
}
}
// MARK: - KVO
extension YHWebViewController {
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard (object as? WKWebView) == webView else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
switch keyPath {
case "estimatedProgress":
let progress = Float(self.webView.estimatedProgress)
progressView.setProgress(progress, animated: true)
if progress >= 1.0 {
UIView.animate(withDuration: 0.3, delay: 0.1, options: .curveEaseOut, animations: {
self.progressView.alpha = 0
}, completion: { (finish) in
self.progressView.setProgress(0.0, animated: false)
})
}else{
self.progressView.alpha = 1
}
break
case "title":
self.navigationItem.title = self.webView.title
break
default:
break
}
}
}
extension YHWebViewController : WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "methodForJsInvoke" {
print("message.name = \(message.name)")
print("message.body = \(message.body)")
/*
var action = {
"type": "2", //业务类型
"param": {"url":"https://www.baidu.com/","key2":"value2",.....}, //业务参数
}
*/
if message.body is [String : Any] == true {
let dic = message.body as! [String : Any]
if let type = dic["type"] as? String {
var param = dic["param"] as? Dictionary<String,Any>
if let temp = param {
param = temp
} else {
param = [:]
}
var title : String = type
switch type {
case "wxShare":
//微信分享
break
case "tabbar":
//tabbar 显示 隐藏
if let show = param?["show"] as? String {
if show == "1" {
NotificationCenter.default.post(name: BsConstant.BsNotification.tabBarShowNotification, object: nil)
} else {
NotificationCenter.default.post(name: BsConstant.BsNotification.tabBarHideNotification, object: nil)
}
}
break
case "goMap":
//地图导航
break
case "appLogin":
//app登录
DispatchQueue.main.async {
// BsLoginManager.shared.showLoginVC(invokeVC: self) {
// //登录成功后的回调
// self.appPassTokenToH5()
// }
}
return
case "callPhone":
//拨打电话
if let phone = param?["phone"] as? String {
phone.makePhone()
}
return
case "goBack":
self.goBack()
return
default:
print("未知的type 类型 \(type)")
break
}
var content : String = "无参数"
if let param = dic["param"] as? Dictionary<String,Any> {
content = BSJsonUtil.dictionaryToJson(param as NSDictionary)
}
title = title + "\n 参数是:" + content
BsHUD.flash(message: title)
}
} else {
print("h5返回数据可能有问题")
}
}
}
}
// MARK: - BsNavigationControllerDelegate
extension YHWebViewController : BsNavigationControllerDelegate {
func canBack() -> Bool {
goBack()
return false
}
override func callBackAction() {
_ = canBack()
}
}
//
// BaseModel.swift
// DouYuUI
//
// Created by davidhuang on 2022/8/15.
// Copyright © 2022 davidhuang. All rights reserved.
//
import UIKit
import HandyJSON
// MARK: - JSon解析模型的基类
class BsBaseModel: HandyJSON {
var data : Any?
var errorCode : Int = -1
var errorMessage : String = ""
var success : Bool = false
// MARK: - 使用 HandyJSON 进行解析
required init() {}
}
//
// ExampleBasicContentView.swift
// ESTabBarControllerExample
//
// Created by lihao on 2017/2/9.
// Copyright © 2018年 Egg Swift. All rights reserved.
//
import UIKit
import ESTabBarController_swift
class YHBasicContentView: ESTabBarItemContentView {
override init(frame: CGRect) {
super.init(frame: frame)
textColor = UIColor.init(white: 175.0 / 255.0, alpha: 1.0)
highlightTextColor = .commonColor100
iconColor = UIColor.init(white: 175.0 / 255.0, alpha: 1.0)
highlightIconColor = .commonColor100
titleLabel.font = UIFont.PFSCR(ofSize: 10)
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//
// ExampleBouncesContentView.swift
// ESTabBarControllerExample
//
// Created by lihao on 2017/2/9.
// Copyright © 2018年 Egg Swift. All rights reserved.
//
import UIKit
class YHBouncesContentView: YHBasicContentView {
public var duration = 0.3
override init(frame: CGRect) {
super.init(frame: frame)
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func selectAnimation(animated: Bool, completion: (() -> ())?) {
self.bounceAnimation()
completion?()
}
override func reselectAnimation(animated: Bool, completion: (() -> ())?) {
self.bounceAnimation()
completion?()
}
func bounceAnimation() {
let impliesAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
impliesAnimation.values = [1.0 ,1.4, 0.9, 1.15, 0.95, 1.02, 1.0]
impliesAnimation.duration = duration * 2
impliesAnimation.calculationMode = CAAnimationCalculationMode.cubic
imageView.layer.add(impliesAnimation, forKey: nil)
}
}
//
// YHNoDataTipView.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/10/25.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
class YHNoDataTipView: UIView {
public var reloadBlock: (()->())?
private var imageStr: String = "no-order-bkg-icon"
private var title: String = "暂无数据"
private lazy var imageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "no-order-bkg-icon"))
return imageView
}()
private lazy var titleLabel: UILabel = {
let label = UILabel(text: "暂无数据", font: UIFont.PFSCR(ofSize: 14), color: UIColor.commonColor45)
return label
}()
private lazy var descLabel: UILabel = {
let label = UILabel(text: "", font: UIFont.PFSCR(ofSize: 12), color: UIColor.commonColor45)
return label
}()
private lazy var reloadBtn: UIButton = {
let btn = UIButton.bs_button(title: "重新加载", font: UIFont.PFSCM(ofSize: 12), normalColor: UIColor.commonColor100)
btn.layer.cornerRadius = 6
btn.layer.masksToBounds = true
btn.layer.borderWidth = 1
btn.layer.borderColor = UIColor.commonColor10.cgColor
btn.addTarget(self, action: #selector(onReloadData), for: .touchUpInside)
return btn
}()
override init(frame: CGRect) {
super.init(frame: frame)
initView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func setup(image: String? = nil, title: String) {
if let image = image {
imageView.image = UIImage(named: image)
self.imageStr = image
}
titleLabel.text = title
self.title = title
}
override var isHidden: Bool {
didSet {
if isHidden == false {
if YHNetworkStatusManager.shared.isNetWorkOK == true {
imageView.image = UIImage(named: self.imageStr)
titleLabel.text = title
descLabel.isHidden = true
reloadBtn.isHidden = true
} else {
imageView.image = UIImage(named: "no_data_network_icon")
titleLabel.text = "网络好像断开了哦"
descLabel.isHidden = false
reloadBtn.isHidden = false
}
}
}
}
}
// MARK: - 提供快速创建的类方法
extension YHNoDataTipView {
private func initView() {
self.addSubview(imageView)
self.addSubview(titleLabel)
self.addSubview(descLabel)
self.addSubview(reloadBtn)
let imageViewTop = 132 / (UIDevice.kScreenW - UIDevice.bs_navigationFullHeight())
imageView.snp.makeConstraints { make in
make.top.equalToSuperview().offset(imageViewTop)
make.centerX.equalToSuperview()
make.width.height.equalTo(165)
}
titleLabel.snp.makeConstraints { make in
make.top.equalTo(imageView.snp.bottom)
make.centerX.equalToSuperview()
}
descLabel.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(4)
make.centerX.equalToSuperview()
}
reloadBtn.snp.makeConstraints { make in
make.top.equalTo(descLabel.snp.bottom).offset(16)
make.centerX.equalToSuperview()
make.width.equalTo(80)
make.height.equalTo(28)
}
descLabel.isHidden = true
reloadBtn.isHidden = true
}
// class func noDataTipView() -> YHNoDataTipView {
// return YHNoDataTipView()
// //return Bundle.main.loadNibNamed("YHNoDataTipView", owner: nil, options: nil)?.first as! YHNoDataTipView
// }
override func layoutSubviews() {
super.layoutSubviews()
let imageViewTop = (132 + UIDevice.bs_navigationFullHeight()) / UIDevice.kScreenH * self.frame.size.height
imageView.snp.updateConstraints { make in
make.top.equalToSuperview().offset(imageViewTop)
}
}
@objc private func onReloadData() {
reloadBlock?()
}
}
//
// AlignedCollectionViewFlowLayout.swift
//
// Created by Mischa Hildebrand on 12/04/2017.
// Copyright © 2017 Mischa Hildebrand.
//
// Licensed under the terms of the MIT license:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
// MARK: - 🦆 Type definitions
/// An abstract protocol that defines an alignment.
protocol Alignment {}
/// Defines a horizontal alignment for UI elements.
public enum HorizontalAlignment: Alignment {
case left
case right
case leading
case trailing
case justified
}
/// Defines a vertical alignment for UI elements.
public enum VerticalAlignment: Alignment {
case top
case center
case bottom
}
/// A horizontal alignment used internally by `AlignedCollectionViewFlowLayout`
/// to layout the items, after resolving layout direction specifics.
private enum EffectiveHorizontalAlignment: Alignment {
case left
case right
case justified
}
/// Describes an axis with respect to which items can be aligned.
private struct AlignmentAxis<A: Alignment> {
/// Determines how items are aligned relative to the axis.
let alignment: A
/// Defines the position of the axis.
/// * If the `Alignment` is horizontal, the alignment axis is vertical and this is the position on the `x` axis.
/// * If the `Alignment` is vertical, the alignment axis is horizontal and this is the position on the `y` axis.
let position: CGFloat
}
// MARK: - Flow Layout
/// A `UICollectionViewFlowLayout` subclass that gives you control
/// over the horizontal and vertical alignment of the cells.
/// You can use it to align the cells like words in a left- or right-aligned text
/// and you can specify how the cells are vertically aligned in their row.
open class AlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {
// MARK: - 🔶 Properties
/// Determines how the cells are horizontally aligned in a row.
/// - Note: The default is `.justified`.
public var horizontalAlignment: HorizontalAlignment = .justified
/// Determines how the cells are vertically aligned in a row.
/// - Note: The default is `.center`.
public var verticalAlignment: VerticalAlignment = .center
/// The `horizontalAlignment` with its layout direction specifics resolved,
/// i.e. `.leading` and `.trailing` alignments are mapped to `.left` or `right`,
/// depending on the current layout direction.
fileprivate var effectiveHorizontalAlignment: EffectiveHorizontalAlignment {
var trivialMapping: [HorizontalAlignment: EffectiveHorizontalAlignment] {
return [
.left: .left,
.right: .right,
.justified: .justified
]
}
let layoutDirection = UIApplication.shared.userInterfaceLayoutDirection
switch layoutDirection {
case .leftToRight:
switch horizontalAlignment {
case .leading:
return .left
case .trailing:
return .right
default:
break
}
case .rightToLeft:
switch horizontalAlignment {
case .leading:
return .right
case .trailing:
return .left
default:
break
}
}
// It's safe to force-unwrap as `.leading` and `.trailing` are covered
// above and the `trivialMapping` dictionary contains all other keys.
return trivialMapping[horizontalAlignment] ?? .left
}
/// The vertical axis with respect to which the cells are horizontally aligned.
/// For a `justified` alignment the alignment axis is not defined and this value is `nil`.
fileprivate var alignmentAxis: AlignmentAxis<HorizontalAlignment>? {
switch effectiveHorizontalAlignment {
case .left:
return AlignmentAxis(alignment: HorizontalAlignment.left, position: sectionInset.left)
case .right:
guard let collectionViewWidth = collectionView?.frame.size.width else {
return nil
}
return AlignmentAxis(alignment: HorizontalAlignment.right, position: collectionViewWidth - sectionInset.right)
default:
return nil
}
}
/// The width of the area inside the collection view that can be filled with cells.
private var contentWidth: CGFloat? {
guard let collectionViewWidth = collectionView?.frame.size.width else {
return nil
}
return collectionViewWidth - sectionInset.left - sectionInset.right
}
// MARK: - 👶 Initialization
/// The designated initializer.
///
/// - Parameters:
/// - horizontalAlignment: Specifies how the cells are horizontally aligned in a row. --
/// (Default: `.justified`)
/// - verticalAlignment: Specified how the cells are vertically aligned in a row. --
/// (Default: `.center`)
public init(horizontalAlignment: HorizontalAlignment = .justified, verticalAlignment: VerticalAlignment = .center) {
super.init()
self.horizontalAlignment = horizontalAlignment
self.verticalAlignment = verticalAlignment
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// MARK: - 🅾️ Overrides
override open func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
// 💡 IDEA:
// The approach for computing a cell's frame is to create a rectangle that covers the current line.
// Then we check if the preceding cell's frame intersects with this rectangle.
// If it does, the current item is not the first item in the line. Otherwise it is.
// (Vice-versa for right-aligned cells.)
//
// +---------+----------------------------------------------------------------+---------+
// | | | |
// | | +------------+ | |
// | | | | | |
// | section |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| section |
// | inset | |intersection| | | line rect | inset |
// | |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| |
// | (left) | | | current item | (right) |
// | | +------------+ | |
// | | previous item | |
// +---------+----------------------------------------------------------------+---------+
//
// ℹ️ We need this rather complicated approach because the first item in a line
// is not always left-aligned and the last item in a line is not always right-aligned:
// If there is only one item in a line UICollectionViewFlowLayout will center it.
// We may not change the original layout attributes or UICollectionViewFlowLayout might complain.
guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
return nil
}
// For a justified layout there's nothing to do here
// as UICollectionViewFlowLayout justifies the items in a line by default.
if horizontalAlignment != .justified {
layoutAttributes.alignHorizontally(collectionViewLayout: self)
}
// For a vertically centered layout there's nothing to do here
// as UICollectionViewFlowLayout center-aligns the items in a line by default.
if verticalAlignment != .center {
layoutAttributes.alignVertically(collectionViewLayout: self)
}
return layoutAttributes
}
override open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// We may not change the original layout attributes or UICollectionViewFlowLayout might complain.
let layoutAttributesObjects = copy(super.layoutAttributesForElements(in: rect))
layoutAttributesObjects?.forEach({ (layoutAttributes) in
setFrame(forLayoutAttributes: layoutAttributes)
})
return layoutAttributesObjects
}
// MARK: - 👷 Private layout helpers
/// Sets the frame for the passed layout attributes object by calling the `layoutAttributesForItem(at:)` function.
private func setFrame(forLayoutAttributes layoutAttributes: UICollectionViewLayoutAttributes) {
if layoutAttributes.representedElementCategory == .cell { // Do not modify header views etc.
let indexPath = layoutAttributes.indexPath
if let newFrame = layoutAttributesForItem(at: indexPath)?.frame {
layoutAttributes.frame = newFrame
}
}
}
/// A function to access the `super` implementation of `layoutAttributesForItem(at:)` externally.
///
/// - Parameter indexPath: The index path of the item for which to return the layout attributes.
/// - Returns: The unmodified layout attributes for the item at the specified index path
/// as computed by `UICollectionViewFlowLayout`.
fileprivate func originalLayoutAttribute(forItemAt indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return super.layoutAttributesForItem(at: indexPath)
}
/// Determines if the `firstItemAttributes`' frame is in the same line
/// as the `secondItemAttributes`' frame.
///
/// - Parameters:
/// - firstItemAttributes: The first layout attributes object to be compared.
/// - secondItemAttributes: The second layout attributes object to be compared.
/// - Returns: `true` if the frames of the two layout attributes are in the same line, else `false`.
/// `false` is also returned when the layout's `collectionView` property is `nil`.
fileprivate func isFrame(for firstItemAttributes: UICollectionViewLayoutAttributes, inSameLineAsFrameFor secondItemAttributes: UICollectionViewLayoutAttributes) -> Bool {
guard let lineWidth = contentWidth else {
return false
}
let firstItemFrame = firstItemAttributes.frame
let lineFrame = CGRect(x: sectionInset.left,
y: firstItemFrame.origin.y,
width: lineWidth,
height: firstItemFrame.size.height)
return lineFrame.intersects(secondItemAttributes.frame)
}
/// Determines the layout attributes objects for all items displayed in the same line as the item
/// represented by the passed `layoutAttributes` object.
///
/// - Parameter layoutAttributes: The layout attributed that represents the reference item.
/// - Returns: The layout attributes objects representing all other items in the same line.
/// The passed `layoutAttributes` object itself is always contained in the returned array.
fileprivate func layoutAttributes(forItemsInLineWith layoutAttributes: UICollectionViewLayoutAttributes) -> [UICollectionViewLayoutAttributes] {
guard let lineWidth = contentWidth else {
return [layoutAttributes]
}
var lineFrame = layoutAttributes.frame
lineFrame.origin.x = sectionInset.left
lineFrame.size.width = lineWidth
return super.layoutAttributesForElements(in: lineFrame) ?? []
}
/// Copmutes the alignment axis with which to align the items represented by the `layoutAttributes` objects vertically.
///
/// - Parameter layoutAttributes: The layout attributes objects to be vertically aligned.
/// - Returns: The axis with respect to which the layout attributes can be aligned
/// or `nil` if the `layoutAttributes` array is empty.
private func verticalAlignmentAxisForLine(with layoutAttributes: [UICollectionViewLayoutAttributes]) -> AlignmentAxis<VerticalAlignment>? {
guard let firstAttribute = layoutAttributes.first else {
return nil
}
switch verticalAlignment {
case .top:
let minY = layoutAttributes.reduce(CGFloat.greatestFiniteMagnitude) { min($0, $1.frame.minY) }
return AlignmentAxis(alignment: .top, position: minY)
case .bottom:
let maxY = layoutAttributes.reduce(0) { max($0, $1.frame.maxY) }
return AlignmentAxis(alignment: .bottom, position: maxY)
default:
let centerY = firstAttribute.center.y
return AlignmentAxis(alignment: .center, position: centerY)
}
}
/// Computes the axis with which to align the item represented by the `currentLayoutAttributes` vertically.
///
/// - Parameter currentLayoutAttributes: The layout attributes representing the item to be vertically aligned.
/// - Returns: The axis with respect to which the item can be aligned.
fileprivate func verticalAlignmentAxis(for currentLayoutAttributes: UICollectionViewLayoutAttributes) -> AlignmentAxis<VerticalAlignment> {
let layoutAttributesInLine = layoutAttributes(forItemsInLineWith: currentLayoutAttributes)
// It's okay to force-unwrap here because we pass a non-empty array.
let axis = verticalAlignmentAxisForLine(with: layoutAttributesInLine) ?? AlignmentAxis(alignment: VerticalAlignment.top, position: 0)
return axis
}
/// Creates a deep copy of the passed array by copying all its items.
///
/// - Parameter layoutAttributesArray: The array to be copied.
/// - Returns: A deep copy of the passed array.
private func copy(_ layoutAttributesArray: [UICollectionViewLayoutAttributes]?) -> [UICollectionViewLayoutAttributes]? {
return layoutAttributesArray?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes]
}
}
// MARK: - 👷 Layout attributes helpers
fileprivate extension UICollectionViewLayoutAttributes {
private var currentSection: Int {
return indexPath.section
}
private var currentItem: Int {
return indexPath.item
}
/// The index path for the item preceding the item represented by this layout attributes object.
private var precedingIndexPath: IndexPath {
return IndexPath(item: currentItem - 1, section: currentSection)
}
/// The index path for the item following the item represented by this layout attributes object.
private var followingIndexPath: IndexPath {
return IndexPath(item: currentItem + 1, section: currentSection)
}
/// Checks if the item represetend by this layout attributes object is the first item in the line.
///
/// - Parameter collectionViewLayout: The layout for which to perform the check.
/// - Returns: `true` if the represented item is the first item in the line, else `false`.
func isRepresentingFirstItemInLine(collectionViewLayout: AlignedCollectionViewFlowLayout) -> Bool {
if currentItem <= 0 {
return true
}
else {
if let layoutAttributesForPrecedingItem = collectionViewLayout.originalLayoutAttribute(forItemAt: precedingIndexPath) {
return !collectionViewLayout.isFrame(for: self, inSameLineAsFrameFor: layoutAttributesForPrecedingItem)
}
else {
return true
}
}
}
/// Checks if the item represetend by this layout attributes object is the last item in the line.
///
/// - Parameter collectionViewLayout: The layout for which to perform the check.
/// - Returns: `true` if the represented item is the last item in the line, else `false`.
func isRepresentingLastItemInLine(collectionViewLayout: AlignedCollectionViewFlowLayout) -> Bool {
guard let itemCount = collectionViewLayout.collectionView?.numberOfItems(inSection: currentSection) else {
return false
}
if currentItem >= itemCount - 1 {
return true
}
else {
if let layoutAttributesForFollowingItem = collectionViewLayout.originalLayoutAttribute(forItemAt: followingIndexPath) {
return !collectionViewLayout.isFrame(for: self, inSameLineAsFrameFor: layoutAttributesForFollowingItem)
}
else {
return true
}
}
}
/// Moves the layout attributes object's frame so that it is aligned horizontally with the alignment axis.
func align(toAlignmentAxis alignmentAxis: AlignmentAxis<HorizontalAlignment>) {
switch alignmentAxis.alignment {
case .left:
frame.origin.x = alignmentAxis.position
case .right:
frame.origin.x = alignmentAxis.position - frame.size.width
default:
break
}
}
/// Moves the layout attributes object's frame so that it is aligned vertically with the alignment axis.
func align(toAlignmentAxis alignmentAxis: AlignmentAxis<VerticalAlignment>) {
switch alignmentAxis.alignment {
case .top:
frame.origin.y = alignmentAxis.position
case .bottom:
frame.origin.y = alignmentAxis.position - frame.size.height
default:
center.y = alignmentAxis.position
}
}
/// Positions the frame right of the preceding item's frame, leaving a spacing between the frames
/// as defined by the collection view layout's `minimumInteritemSpacing`.
///
/// - Parameter collectionViewLayout: The layout on which to perfom the calculations.
private func alignToPrecedingItem(collectionViewLayout: AlignedCollectionViewFlowLayout) {
let itemSpacing = collectionViewLayout.minimumInteritemSpacing
if let precedingItemAttributes = collectionViewLayout.layoutAttributesForItem(at: precedingIndexPath) {
frame.origin.x = precedingItemAttributes.frame.maxX + itemSpacing
}
}
/// Positions the frame left of the following item's frame, leaving a spacing between the frames
/// as defined by the collection view layout's `minimumInteritemSpacing`.
///
/// - Parameter collectionViewLayout: The layout on which to perfom the calculations.
private func alignToFollowingItem(collectionViewLayout: AlignedCollectionViewFlowLayout) {
let itemSpacing = collectionViewLayout.minimumInteritemSpacing
if let followingItemAttributes = collectionViewLayout.layoutAttributesForItem(at: followingIndexPath) {
frame.origin.x = followingItemAttributes.frame.minX - itemSpacing - frame.size.width
}
}
/// Aligns the frame horizontally as specified by the collection view layout's `horizontalAlignment`.
///
/// - Parameters:
/// - collectionViewLayout: The layout providing the alignment information.
func alignHorizontally(collectionViewLayout: AlignedCollectionViewFlowLayout) {
guard let alignmentAxis = collectionViewLayout.alignmentAxis else {
return
}
switch collectionViewLayout.effectiveHorizontalAlignment {
case .left:
if isRepresentingFirstItemInLine(collectionViewLayout: collectionViewLayout) {
align(toAlignmentAxis: alignmentAxis)
} else {
alignToPrecedingItem(collectionViewLayout: collectionViewLayout)
}
case .right:
if isRepresentingLastItemInLine(collectionViewLayout: collectionViewLayout) {
align(toAlignmentAxis: alignmentAxis)
} else {
alignToFollowingItem(collectionViewLayout: collectionViewLayout)
}
default:
return
}
}
/// Aligns the frame vertically as specified by the collection view layout's `verticalAlignment`.
///
/// - Parameter collectionViewLayout: The layout providing the alignment information.
func alignVertically(collectionViewLayout: AlignedCollectionViewFlowLayout) {
let alignmentAxis = collectionViewLayout.verticalAlignmentAxis(for: self)
align(toAlignmentAxis: alignmentAxis)
}
}
//
// AppDelegate + UIAppearance.swift
// SleepDoctor
//
// Created by DAVIDiOS1 on 2020/12/24.
// Copyright © 2020 sumian. All rights reserved.
//
import UIKit
// MARK: - 设置导航栏样式
extension AppDelegate{
func customAppearance() {
setNavigationBarAppearanceWhenContainedInInstancesOf(containers: [YHNavigationController.self])
setNavigationBarAppearanceWhenContainedInInstancesOf(containers: [YHBaseViewController.self])
}
func setNavigationBarAppearanceWhenContainedInInstancesOf(containers: [UIAppearanceContainer.Type]){
let navigationBarAppearance = UINavigationBar.appearance(whenContainedInInstancesOf: containers)
navigationBarAppearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor : UIColor.commonColor100,NSAttributedString.Key.font:UIFont.PFSCM(ofSize: 16)]
navigationBarAppearance.barTintColor = UIColor.yellow
navigationBarAppearance.tintColor = UIColor.commonColor100
navigationBarAppearance.backIndicatorImage = UIImage(named: "back_icon")
navigationBarAppearance.backIndicatorTransitionMaskImage = UIImage(named: "back_icon")
navigationBarAppearance.setBackgroundImage(UIImage.from(color: UIColor.clear),for: .default)
navigationBarAppearance.isOpaque = true
navigationBarAppearance.shadowImage = nil
//隐藏返回文字
if #available(iOS 13, *) {
UIBarButtonItem.appearance().setBackButtonTitlePositionAdjustment(UIOffset( horizontal: -UIDevice.kScreenW, vertical: 0), for: .default)
}
let barItemAppearance = UIBarButtonItem.appearance(whenContainedInInstancesOf: containers)
barItemAppearance.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor.clear,NSAttributedString.Key.font:UIFont.systemFont(ofSize: 14)], for: UIControl.State.normal)
barItemAppearance.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor.clear,NSAttributedString.Key.font:UIFont.systemFont(ofSize: 14)], for: UIControl.State.highlighted)
}
}
// MARK: - Pay 支付
extension AppDelegate {
func payCallback(url: URL) {
// //支付宝支付
// if url.host == "safepay" {
// AlipaySDK.defaultService().processOrder(withPaymentResult: url, standbyCallback: { resultDic in
// let status = resultDic?["resultStatus"] as? String ?? ""
// var success: Bool = false
// if status == "9000" {
// success = true
// print("支付宝: 支付成功")
// } else {
// success = false
// print("支付宝: 支付失败")
// }
// NotificationCenter.default.post(name: BsConstant.BsNotification.payResultNotification, object: success)
// })
// }
}
}
//
// BsCacheTool.swift
// BaiSiSMApp
//
// Created by DAVID on 2023/1/4.
// Copyright © 2023 www.sumian.com. All rights reserved.
//
import Foundation
open class BsCacheTool: NSObject {
/// 计算缓存大小
public static var cacheSize: String{
get{
// 路径
let basePath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
let fileManager = FileManager.default
// 遍历出所有缓存文件加起来的大小
func caculateCache() -> CGFloat{
var total: CGFloat = 0
if fileManager.fileExists(atPath: basePath!){
let childrenPath = fileManager.subpaths(atPath: basePath!)
if childrenPath != nil{
for path in childrenPath!{
if path != "iconName.png" && path != "Collect.json" {
let childPath = basePath!.appending("/").appending(path)
do{
let attr:NSDictionary = try fileManager.attributesOfItem(atPath: childPath) as NSDictionary
if let fileSize = attr["NSFileSize"] as? CGFloat {
total += fileSize
}
}catch _{
}
}
}
}
}
// 缓存文件大小
return total
}
// 调用函数
let totalCache = caculateCache()
return NSString(format: "%.2fM", totalCache / 1024.0 / 1024.0 ) as String
}
}
/// 清除缓存
///
/// - returns: 是否清理成功
public class func clearCache() -> Bool {
var result = true
// 取出cache文件夹目录 缓存文件都在这个目录下
let cachePath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
// 取出文件夹下所有文件数组
let fileArr = FileManager.default.subpaths(atPath: cachePath!)
// 遍历删除
for file in fileArr! {
if file != "iconName.png" && file != "Collect.json" {
// 拼接文件路径
let path = cachePath?.appending("/\(file)")
if FileManager.default.fileExists(atPath: path!) {
// 循环删除
do {
try FileManager.default.removeItem(atPath: path!)
} catch {
// 删除失败
result = false
}
}
}
}
return result
}
}
//
// BsGestureTableView.swift
// GDKit
//
// Created by GDKit on 01/11/2022.
// Copyright (c) 2022 GDKit. All rights reserved.
//
import UIKit
open class BsGestureTableView: UITableView {
}
// MARK: - UIGestureRecognizerDelegate
extension BsGestureTableView: UIGestureRecognizerDelegate {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
open override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
open class BsGestureScrollView: UIScrollView {
}
// MARK: - UIGestureRecognizerDelegate
extension BsGestureScrollView: UIGestureRecognizerDelegate {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
open override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
open class BsGestureCollectionView: UICollectionView {
}
// MARK: - UIGestureRecognizerDelegate
extension BsGestureCollectionView: UIGestureRecognizerDelegate {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
open override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
//
// BsOverlayView.swift
// BaiSiSMApp
//
// Created by DAVID on 2022/11/22.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
class BsOverlayView: UIView {
// MARK: - 属性
public var overlayView:UIControl! //遮罩层view
private var keyWindow : UIWindow?
// MARK: - 生命周期函数
override init(frame: CGRect) {
super.init(frame: frame)
setupOverlayView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupOverlayView() {
// 1 获取window
if (keyWindow == nil) {
self.keyWindow = UIApplication.shared.bsKeyWindow()
}
// 2.遮罩view
overlayView = UIControl.init(frame: UIScreen.main.bounds)
overlayView?.backgroundColor = UIColor.init(red: 0, green: 0, blue: 0, alpha: 0.5)
overlayView?.addTarget(self, action: #selector(hide), for: .touchUpInside)
overlayView?.alpha = 0
}
// MARK: - ACTIONS
//显示
public func show() {
keyWindow?.addSubview(overlayView!)
keyWindow?.addSubview(self)
UIView.animate(withDuration: 0.25, animations: {
self.overlayView?.alpha = 1.0
var frame = self.frame
frame.origin.y = UIScreen.main.bounds.size.height - self.bounds.size.height
self.frame = frame
}) { (isFinished) in
//
}
}
//隐藏
@objc public func hide() {
UIView.animate(withDuration: 0.25, animations: {
self.overlayView?.alpha = 0
var frame = self.frame
frame.origin.y = UIScreen.main.bounds.size.height
self.frame = frame
}) { (isFinished) in
self.overlayView?.removeFromSuperview()
self.removeFromSuperview()
}
}
// MARK: - 私有方法
// MARK: - 事件响应包括手势和按钮等
// MARK: - UITableViewDataSource 和 UITableViewDelegate
}
//
// Date-Extension.swift
// DouYuUI
//
// Created by davidhuang on 2022/8/11.
// Copyright © 2022 davidhuang. All rights reserved.
//
import UIKit
extension Date {
static func getCurrentDate() -> String {
let now = Int(Date.timeIntervalSinceReferenceDate)
return "\(now)"
}
init(_ dateString: String, dateFormat: String = "yyyy-MM-dd") {
let df = DateFormatter()
df.dateFormat = dateFormat
let date = df.date(from: dateString)!
self.init(timeInterval: 0, since: date)
}
}
//
// NSAttributedString+Extension.swift
// BaiSiSMApp
//
// Created by DAVID on 2022/12/6.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
extension NSAttributedString {
//添加富文本
public class func bs_create(string str: String, font: UIFont? = nil, color textColor: UIColor? = nil, url: String? = nil, lineSpacing: CGFloat? = nil) -> NSAttributedString {
var attributes: [NSAttributedString.Key: Any] = [:]
if let font = font {
attributes[.font] = font
}
if let textColor = textColor {
attributes[.foregroundColor] = textColor
}
if let url = url {
//添加链接文本
attributes[.link] = url
}
if let lineSpacing = lineSpacing {
//格式调整
let style = NSMutableParagraphStyle()
/**调行间距*/
style.lineSpacing = lineSpacing
style.alignment = .left
attributes[.paragraphStyle] = style
}
let attributedString = NSAttributedString(string: str, attributes: attributes)
return attributedString
}
//添加图片
public class func gd_create(image: UIImage) -> NSAttributedString {
let textAttachment = NSTextAttachment()
textAttachment.image = image
let attrStringWithImage = NSAttributedString(attachment: textAttachment)
return attrStringWithImage
}
}
extension NSAttributedString {
/// 富文本转html字符串
public class func attriToStr(attri: NSAttributedString) -> String {
let tempDic: [NSAttributedString.DocumentAttributeKey : Any] =
[NSAttributedString.DocumentAttributeKey.documentType:
NSAttributedString.DocumentType.html,
NSAttributedString.DocumentAttributeKey.characterEncoding:
String.Encoding.utf8.rawValue]
do {
let htmlData = try attri.data(from: NSRange(location: 0, length: attri.length-1), documentAttributes: tempDic)
return String(data: htmlData, encoding: String.Encoding.utf8) ?? ""
} catch {
return ""
}
}
/// 字符串转富文本
public class func strToAttri(htmlStr: String) -> NSAttributedString {
guard let data = htmlStr.data(using: String.Encoding.unicode) else {
return NSAttributedString()
}
do {
let attri = try NSAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil)
return attri
} catch {
return NSAttributedString()
}
}
}
extension NSMutableAttributedString {
//添加图片和点击事件
public class func bs_create(image: UIImage) -> NSMutableAttributedString {
let textAttachment = NSTextAttachment()
textAttachment.image = image
let attrStringWithImage = NSMutableAttributedString(attachment: textAttachment)
attrStringWithImage.addAttributes([.link: "checkbox://"], range: NSMakeRange(0, attrStringWithImage.length))
return attrStringWithImage
}
public func setStyle(lineSpacing: CGFloat) {
//格式调整
let style = NSMutableParagraphStyle()
/**调行间距*/
style.lineSpacing = lineSpacing
style.alignment = .left
self.addAttributes([NSAttributedString.Key.paragraphStyle : style], range: NSRange(location: 0, length: self.string.count))
}
}
//
// String-Extension.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/10/12.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
extension String {
// MARK: - 拨打电话
func makePhone() {
let phone = "telprompt://" + self
if UIApplication.shared.canOpenURL(URL(string: phone)!) == true {
UIApplication.shared.open(URL(string: phone)!, options: [:], completionHandler: nil)
}
}
// MARK: - 判断是否为手机号码
func isMobile() -> Bool {
let reg = "^1[3456789]\\d{9}$"
let regextestmobile = NSPredicate(format: "SELF MATCHES %@",reg)
return regextestmobile.evaluate(with: self);
}
// MARK: - 判断密码是否合法
//密码至少包含一个数字,一个字母,一个特殊字符
func isPwdLegal() -> Bool {
let reg = "^(?=.*)(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[-~!@#$%^&*:;,.=?$\\x22]).{8,20}$"
let regextestmobile = NSPredicate(format: "SELF MATCHES %@",reg)
return regextestmobile.evaluate(with: self);
}
// MARK: - 是否包含字符
func isIncludeChinese() -> Bool {
for char in self {
if ("\u{4E00}" <= char && char <= "\u{9FA5}") {
return true
}
}
return false
}
func phoneNumberForUI() -> String {
let phone = self.replacingOccurrences(of: " ", with: "")
if phone.count == 11 {
let st1 = phone.slicing(from: 0, length: 3)
let st2 = phone.slicing(from:7,length: 4)
return st1! + "****" + st2!
}
return self
}
var unicodeDescription: String {
return self.stringByReplaceUnicode
}
var stringByReplaceUnicode: String {
let jsonDict = try? JSONSerialization.jsonObject(
with: self.data(using: String.Encoding.utf8, allowLossyConversion: true)!,
options: JSONSerialization.ReadingOptions.mutableLeaves)
let jsonData = try? JSONSerialization.data(withJSONObject: jsonDict as Any, options: .prettyPrinted)
let string_r = String(data: jsonData!, encoding: String.Encoding.utf8)
return string_r ?? self
}
//时间戳转Date
func stringConvertDate(dateFormat:String="yyyy-MM-dd HH:mm:ss") -> Date {
let timeZone = TimeZone.init(identifier: "UTC") //这是重点
let dateFormatter = DateFormatter.init()
dateFormatter.timeZone = timeZone
dateFormatter.dateFormat = dateFormat
let date = dateFormatter.date(from: self)
return date!
}
//时间戳转Date
func convertDate(dateFormat:String="yyyy-MM-dd HH:mm:ss") -> Date {
let dateFormatter = DateFormatter.init()
dateFormatter.dateFormat = dateFormat
let date = dateFormatter.date(from: self) ?? Date()
return date
}
//时间戳转 DateString
func timeStampToString()->String {
guard let timeStamp = self.cgFloat() else {
return "--"
}
//时间戳为毫秒级要 / 1000, 秒就不用除1000,参数带没带000
let timeSta:TimeInterval = TimeInterval(timeStamp)
let date = NSDate(timeIntervalSince1970: timeSta)
let dateformatter = DateFormatter()
dateformatter.dateFormat="yyyy-MM-dd HH:mm:ss"
return dateformatter.string(from: date as Date)
}
}
// MARK: - 计算文字的宽高
extension String {
/// 计算文字的宽高
///
/// - Parameters:
/// - font: 字体大小
/// - lineSpacing: 行高
/// - constraintRect: 大小范围
/// - Returns: 宽高
public func bs_sizeWithConstrained(_ font: UIFont,
lineSpacing: CGFloat? = nil,
constraintRect: CGSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)) -> CGSize {
var attributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.font: font]
if let lineSpacing = lineSpacing {
//格式调整
let style = NSMutableParagraphStyle()
/**调行间距*/
style.lineSpacing = lineSpacing
style.alignment = .left
attributes[.paragraphStyle] = style
}
let boundingBox = self.boundingRect(
with: constraintRect,
options: NSStringDrawingOptions.usesLineFragmentOrigin,
attributes: attributes,
context: nil)
return boundingBox.size
}
}
// MARK: - 正则获取http
extension String {
//获取http链接字符串的参数
public var urlParameters: [String: String] {
guard let url = URL(string: self) else {
return [:]
}
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
let queryItems = components.queryItems else { return [:] }
return queryItems.reduce(into: [String: String]()) { (result, item) in
result[item.name] = item.value
}
}
///获取http链接字符串的range
public func getHttpRangeOfString() -> [NSTextCheckingResult]? {
let regulaStr = "((http[s]{0,1}|ftp)://[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)|(www.[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)"
do {
let regex = try NSRegularExpression(pattern: regulaStr, options: .caseInsensitive)
let arrayOfAllMatches = regex.matches(in: self, options: NSRegularExpression.MatchingOptions.init(rawValue: 0), range: NSMakeRange(0, self.count))
return arrayOfAllMatches
} catch {
return nil
}
}
}
/// 打印
///
/// - Parameters:
/// - message: 打印内容
/// - file: 所在文件
/// - method: 所在方法
/// - line: 所在行
public func printLog<T>(_ message: T, file: String = #file, method: String = #function, line: Int = #line) {
print("\((file as NSString).lastPathComponent)[\(line)],\(method):\(message)")
}
//
// UIApplication-Extension.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/10/31.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
extension UIApplication {
func bsKeyWindow() -> UIWindow? {
var originalKeyWindow: UIWindow?
#if swift(>=5.1)
if #available(iOS 13, *) {
originalKeyWindow = UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first(where: { $0.isKeyWindow })
} else {
originalKeyWindow = UIApplication.shared.keyWindow
}
#else
originalKeyWindow = UIApplication.shared.keyWindow
#endif
return originalKeyWindow
}
}
//
// UIButton-Extension.swift
// BaiSiSMApp
//
// Created by DAVID on 2023/1/11.
// Copyright © 2023 www.sumian.com. All rights reserved.
//
import UIKit
extension UIButton {
/// 图片在右位置
/// - Parameter spacing: 间距
func iconInRight(with spacing: CGFloat) {
let img_w = self.imageView?.frame.size.width ?? 0
let title_w = self.titleLabel?.frame.size.width ?? 0
self.titleEdgeInsets = UIEdgeInsets(top: 0, left: -(img_w+spacing / 2.0), bottom: 0, right: (img_w+spacing / 2.0))
self.imageEdgeInsets = UIEdgeInsets(top: 0, left: (title_w+spacing / 2.0), bottom: 0, right: -(title_w+spacing / 2.0))
}
/// 图片在左位置
/// - Parameter spacing: 间距
func iconInLeft(spacing: CGFloat) {
self.titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing / 2.0, bottom: 0, right: -spacing / 2.0)
self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -spacing / 2.0, bottom: 0, right: spacing / 2.0)
}
/// 图片在上面
/// - Parameter spacing: 间距
func iconInTop(spacing: CGFloat) {
let img_W = self.imageView?.frame.size.width ?? 0
let img_H = self.imageView?.frame.size.height ?? 0
let tit_W = self.titleLabel?.frame.size.width ?? 0
let tit_H = self.titleLabel?.frame.size.height ?? 0
self.titleEdgeInsets = UIEdgeInsets(top: (tit_H / 2 + spacing / 2), left: -(img_W / 2), bottom: -(tit_H / 2 + spacing / 2), right: (img_W / 2))
self.imageEdgeInsets = UIEdgeInsets(top: -(img_H / 2 + spacing / 2), left: (tit_W / 2), bottom: (img_H / 2 + spacing / 2), right: -(tit_W / 2))
}
/// 图片在 下面
/// - Parameter spacing: 间距
func iconInBottom(spacing: CGFloat) {
let img_W = self.imageView?.frame.size.width ?? 0
let img_H = self.imageView?.frame.size.height ?? 0
let tit_W = self.titleLabel?.frame.size.width ?? 0
let tit_H = self.titleLabel?.frame.size.height ?? 0
self.titleEdgeInsets = UIEdgeInsets(top: -(tit_H / 2 + spacing / 2), left: -(img_W / 2), bottom: (tit_H / 2 + spacing / 2), right: (img_W / 2))
self.imageEdgeInsets = UIEdgeInsets(top: (img_H / 2 + spacing / 2), left: (tit_W / 2), bottom: -(img_H / 2 + spacing / 2), right: -(tit_W / 2))
}
}
// MARK: - Create 初始化
extension UIButton {
public class func bs_button(
title:String? = nil,
selectTitle:String? = nil,
font:UIFont? = nil,
normalColor: UIColor? = nil,
selectColor: UIColor? = nil,
bgNormalColor: UIColor? = nil,
bgSelectColor: UIColor? = nil,
lightNormalColor: UIColor? = nil,
lightSelectColor: UIColor? = nil,
bgLightNormalColor: UIColor? = nil,
bgLightSelectColor: UIColor? = nil) -> UIButton {
let btn = UIButton(type: .custom)
btn.titleLabel?.font = font
btn.setTitle(title, for: .normal)
btn.setTitle(selectTitle, for: .selected)
btn.setTitle(title, for: [.highlighted,.normal])
btn.setTitle(selectTitle, for: [.highlighted,.selected])
btn.setTitleColor(normalColor, for: .normal)
btn.setTitleColor(selectColor, for: .selected)
btn.setTitleColor(lightNormalColor, for: [.highlighted,.normal])
btn.setTitleColor(lightSelectColor, for: [.highlighted,.selected])
btn.setBackgroundImage(bgNormalColor?.imageWithColor(), for: .normal)
btn.setBackgroundImage(bgSelectColor?.imageWithColor(), for: .selected)
btn.setBackgroundImage(bgLightNormalColor?.imageWithColor(), for: [.highlighted,.normal])
if bgLightNormalColor == nil {
btn.setBackgroundImage(bgNormalColor?.withAlphaComponent(0.9).imageWithColor(), for: [.highlighted,.normal])
}
btn.setBackgroundImage(bgLightSelectColor?.imageWithColor(), for: [.highlighted,.selected])
if bgLightSelectColor == nil {
btn.setBackgroundImage(bgSelectColor?.withAlphaComponent(0.9).imageWithColor(), for: [.highlighted,.selected])
}
return btn
}
}
//
// UIColor-Extension.swift
// DouYuUI
//
// Created by davidhuang on 2022/8/9.
// Copyright © 2022 davidhuang. All rights reserved.
//
import UIKit
import SwifterSwift
extension UIColor {
// MARK: - 当前 app 主颜色
static let mainColor : UIColor = UIColor(hexString: "#46E0BB")!
static let commonColor100 : UIColor = UIColor(hexString: "#333333",transparency: 1.0)!
static let commonColor90 : UIColor = UIColor(hexString: "#333333",transparency: 0.9)!
static let commonColor65 : UIColor = UIColor(hexString: "#333333",transparency: 0.65)!
static let commonColor45 : UIColor = UIColor(hexString: "#333333",transparency: 0.45)!
static let commonColor25 : UIColor = UIColor(hexString: "#333333",transparency: 0.25)!
static let commonColor15 : UIColor = UIColor(hexString: "#333333",transparency: 0.15)!
static let commonColor10 : UIColor = UIColor(hexString: "#333333",transparency: 0.10)!
static let commonColor6 : UIColor = UIColor(hexString: "#333333",transparency: 0.06)!
static let commonColor4 : UIColor = UIColor(hexString: "#333333",transparency: 0.04)!
static let commonColor3 : UIColor = UIColor(hexString: "#333333",transparency: 0.03)!
static let backgroundColor : UIColor = UIColor(hexString: "#F2F7F6",transparency: 1.0)!
static let mainTextColor : UIColor = UIColor(hexString: "#00D19F")!
static let selectBgColor : UIColor = UIColor(hexString: "#D9FCF7")!
//黑色 50透明
static let black50Persent : UIColor = UIColor.black.withAlphaComponent(0.5)
public convenience init(r : CGFloat, g : CGFloat, b : CGFloat) {
self.init(red: r / 255.0, green: g / 255.0, blue: b / 255.0, alpha: 1.0)
}
public func imageWithColor(width: Double = 1.0, height: Double = 1.0) -> UIImage {
let rect = CGRect(x: 0.0, y: 0.0, width: width, height: height)
UIGraphicsBeginImageContext(rect.size)
let ctx = UIGraphicsGetCurrentContext()
ctx!.setFillColor(self.cgColor)
ctx!.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
//
// UIDevice-Extension.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/10/11.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
extension UIDevice {
// MARK: - 屏幕size
static let kScreenSize : CGRect = UIScreen.main.bounds
// MARK: - 屏幕宽度
static let kScreenW : CGFloat = UIScreen.main.bounds.width
// MARK: - 屏幕高度
static let kScreenH : CGFloat = UIScreen.main.bounds.height
// MARK: - iOS系统版本号
static let kSystemVersion : String = UIDevice.current.systemVersion
// MARK: - 顶部安全区高度
static func bs_safeDistanceTop() -> CGFloat {
if #available(iOS 13.0, *) {
let scene = UIApplication.shared.connectedScenes.first
guard let windowScene = scene as? UIWindowScene else { return 0 }
guard let window = windowScene.windows.first else { return 0 }
return window.safeAreaInsets.top
}
// else if #available(iOS 11.0, *) {
// guard let window = UIApplication.shared.windows.first else { return 0 }
// return window.safeAreaInsets.top
// }
return 0;
}
// MARK: - 底部安全区高度
static func bs_safeDistanceBottom() -> CGFloat {
if #available(iOS 13.0, *) {
let scene = UIApplication.shared.connectedScenes.first
guard let windowScene = scene as? UIWindowScene else { return 0 }
guard let window = windowScene.windows.first else { return 0 }
return window.safeAreaInsets.bottom
}
// else if #available(iOS 11.0, *) {
// guard let window = UIApplication.shared.windows.first else { return 0 }
// return window.safeAreaInsets.bottom
// }
return 0;
}
// MARK: - 顶部状态栏高度(包括安全区)
static func bs_statusBarHeight() -> CGFloat {
var statusBarHeight: CGFloat = 0
if #available(iOS 13.0, *) {
let scene = UIApplication.shared.connectedScenes.first
guard let windowScene = scene as? UIWindowScene else { return 0 }
guard let statusBarManager = windowScene.statusBarManager else { return 0 }
statusBarHeight = statusBarManager.statusBarFrame.height
}
// else {
// statusBarHeight = UIApplication.shared.statusBarFrame.height
// }
return statusBarHeight
}
// MARK: - 导航栏高度
static func bs_navigationBarHeight() -> CGFloat {
return 44.0
}
// MARK: - 状态栏+导航栏的高度
static func bs_navigationFullHeight() -> CGFloat {
return UIDevice.bs_statusBarHeight() + UIDevice.bs_navigationBarHeight()
}
// MARK: - 底部导航栏高度
static func bs_tabBarHeight() -> CGFloat {
return 49.0
}
// MARK: - 底部导航栏高度(包括安全区)
static func bs_tabBarFullHeight() -> CGFloat {
return UIDevice.bs_tabBarHeight() + UIDevice.bs_safeDistanceBottom()
}
// MARK: - 版本号
static func appVersion() -> String {
let infoDictionary = Bundle.main.infoDictionary
if let infoDictionary = infoDictionary {
let appVersion = infoDictionary["CFBundleShortVersionString"]
return appVersion as! String
}
return " "
}
// MARK: - 内部编译版本号
static func appBuild() -> String {
let infoDictionary = Bundle.main.infoDictionary
if let infoDictionary = infoDictionary {
let appBuild = infoDictionary["CFBundleVersion"]
return appBuild as! String
}
return " "
}
}
//
// UIFont-Extension.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/10/16.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
extension UIFont {
// MARK: - 苹果字体
static func PFSCR(ofSize size : CGFloat = 15) -> UIFont {
return UIFont(name: "PingFangSC-Regular", size: size)!
}
static func PFSCM(ofSize size : CGFloat = 15) -> UIFont {
return UIFont(name: "PingFangSC-Medium", size: size)!
}
// static func PFSCB(ofSize size : CGFloat = 15) -> UIFont {
// return UIFont(name: "PingFangSC-Bold", size: size)!
// }
}
//
// UIImage-Extension.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/11/3.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
extension UIImage {
static func defaultHeadImage() -> UIImage? {
return UIImage(named: "default-head-image")
}
static func from(color: UIColor,
rect: CGRect = CGRect(origin: CGPoint.zero ,size: CGSize(width: 4, height: 4))) -> UIImage {
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context!.setFillColor(color.cgColor)
context!.fill(rect)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!
}
// 修复图片旋转
func fixOrientation() -> UIImage {
if self.imageOrientation == .up {
return self
}
var transform = CGAffineTransform.identity
switch self.imageOrientation {
case .down, .downMirrored:
transform = transform.translatedBy(x: self.size.width, y: self.size.height)
transform = transform.rotated(by: .pi)
break
case .left, .leftMirrored:
transform = transform.translatedBy(x: self.size.width, y: 0)
transform = transform.rotated(by: .pi / 2)
break
case .right, .rightMirrored:
transform = transform.translatedBy(x: 0, y: self.size.height)
transform = transform.rotated(by: -.pi / 2)
break
default:
break
}
switch self.imageOrientation {
case .upMirrored, .downMirrored:
transform = transform.translatedBy(x: self.size.width, y: 0)
transform = transform.scaledBy(x: -1, y: 1)
break
case .leftMirrored, .rightMirrored:
transform = transform.translatedBy(x: self.size.height, y: 0);
transform = transform.scaledBy(x: -1, y: 1)
break
default:
break
}
let ctx = CGContext(data: nil, width: Int(self.size.width), height: Int(self.size.height), bitsPerComponent: self.cgImage!.bitsPerComponent, bytesPerRow: 0, space: self.cgImage!.colorSpace!, bitmapInfo: self.cgImage!.bitmapInfo.rawValue)
ctx?.concatenate(transform)
switch self.imageOrientation {
case .left, .leftMirrored, .right, .rightMirrored:
ctx?.draw(self.cgImage!, in: CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(size.height), height: CGFloat(size.width)))
break
default:
ctx?.draw(self.cgImage!, in: CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(size.width), height: CGFloat(size.height)))
break
}
let cgimg: CGImage = (ctx?.makeImage())!
let img = UIImage(cgImage: cgimg)
return img
}
}
// MARK: - 图片大小编辑
extension UIImage {
//等比例放大(图片居中,增加边缘透明范围)
func bs_scaled(toHeight: CGFloat, opaque: Bool = false) -> UIImage? {
let scale = toHeight / size.height
let newWidth = size.width * scale
UIGraphicsBeginImageContextWithOptions(CGSize(width: newWidth, height: toHeight), opaque, self.scale)
draw(in: CGRect(x: (newWidth - size.width)/2, y: (toHeight - size.height)/2, width: size.width, height: size.height))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
func withBackground(color: UIColor, opaque: Bool = true) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, opaque, scale)
guard let ctx = UIGraphicsGetCurrentContext(), let image = cgImage else { return self }
defer { UIGraphicsEndImageContext() }
let rect = CGRect(origin: .zero, size: size)
ctx.setFillColor(color.cgColor)
ctx.fill(rect)
ctx.concatenate(CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: size.height))
ctx.draw(image, in: rect)
return UIGraphicsGetImageFromCurrentImageContext() ?? self
}
//等比例放大(图片居中,增加边缘透明范围)
func bs_scaled(toWidth: CGFloat, opaque: Bool = false) -> UIImage? {
let scale = toWidth / size.width
let newHeight = size.height * scale
UIGraphicsBeginImageContextWithOptions(CGSize(width: toWidth, height: newHeight), opaque, self.scale)
draw(in: CGRect(x: (toWidth - size.width)/2, y: (newHeight - size.height)/2, width: size.width, height: size.height))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
/// 放大画布大小(图片自定义,增加边缘透明范围)
/// - Parameters:
/// - toSize: 需要的画布大小
/// - rect: 图片在画布的位置
/// - opaque: 不透明/黑色背景
/// - Returns: 图片
func bs_canvas(toSize: CGSize, rect: CGRect, opaque: Bool = false) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(CGSize(width: toSize.width, height: toSize.height), opaque, self.scale)
draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}
//
// UILable-Extension.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/11/16.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
extension UILabel {
public class func createBsTagLabel() -> UILabel {
let temp = UILabel()
temp.textColor = UIColor(hexString: "#333333", transparency: 0.45)
temp.textAlignment = .center
temp.font = UIFont.PFSCM(ofSize: 10)
temp.backgroundColor = UIColor(hexString: "#333333", transparency: 0.04)
temp.layer.cornerRadius = 2
temp.clipsToBounds = true
return temp
}
public class func label(text: String, font: UIFont, color: UIColor, numberOfLines: Int = 0, alignment: NSTextAlignment = .left) -> UILabel {
let label = UILabel()
label.text = text
label.font = font
label.textColor = color
label.numberOfLines = numberOfLines
label.textAlignment = alignment
return label
}
convenience init(text: String, font: UIFont, color: UIColor, numberOfLines: Int = 0, alignment: NSTextAlignment = .left) {
self.init()
self.text = text
self.font = font
self.textColor = color
self.numberOfLines = numberOfLines
self.textAlignment = alignment
}
var requiredWidth: CGFloat {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: CGFloat.greatestFiniteMagnitude, height: frame.height))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.attributedText = attributedText
label.sizeToFit()
return label.frame.width
}
}
//
// UIView-Extension.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/10/16.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
extension UIView {
// MARK: - 旋转
func rotate( begin : Bool = true) {
if begin == true {
let rotation : CABasicAnimation = CABasicAnimation(keyPath:"transform.rotation.z")
rotation.toValue = NSNumber(value: Double.pi * 2)
rotation.duration = 3
rotation.isCumulative = true
rotation.repeatCount = Float.greatestFiniteMagnitude
rotation.fillMode = CAMediaTimingFillMode(rawValue: "forwards")
layer.add(rotation, forKey:"rotationAnimation")
} else {
layer.removeAllAnimations()
}
}
// MARK: - 获取当前UIview 所在的 UIViewController
func getFirstViewController()->UIViewController?{
for view in sequence(first: self.superview, next: {$0?.superview}){
if let responder = view?.next{
if responder.isKind(of: UIViewController.self){
return responder as? UIViewController
}
}
}
return nil
}
// MARK: - UIView 晃动效果
public func shake2(direction:ShakeDirection = .horizontal,times: Int = 5,
interval:TimeInterval = 0.1, delta : CGFloat = 5,completion:(() -> Void)?=nil){
if times == 0 {
self.layer.setAffineTransform(CGAffineTransform.identity)
} else {
UIView.animate(withDuration: interval) {
switch direction {
case .horizontal:
self.layer.setAffineTransform(CGAffineTransform(translationX: delta, y: 0))
break
case .vertical:
self.layer.setAffineTransform(CGAffineTransform(translationX: 0, y: delta))
break
}
} completion: { (finished) in
self.shake2(direction: direction, times: times - 1 , interval: interval, delta: delta * -1, completion: completion)
}
}
}
}
// MARK: - 阴影设置
extension UIView {
@objc public enum ShadowType: Int {
case all = 0 ///四周
case top = 1 ///上方
case left = 2///左边
case right = 3///右边
case bottom = 4///下方
}
///默认设置:黑色阴影, 阴影所占视图的比例
// func shadow(_ type: ShadowType, percent: Float) {
// shadow(type: type, color: .black, opactiy: 0.4, //shadowSize: 4)
//}
///默认设置:黑色阴影
@objc func shadow(_ type: ShadowType) {
shadow(type: type, color: .black, opactiy: 0.1, shadowSize: 4)
}
///阴影设置
@objc public func shadow(type: ShadowType, color: UIColor, opactiy: Float, shadowSize: CGFloat) -> Void {
layer.masksToBounds = false;//必须要等于NO否则会把阴影切割隐藏掉
layer.shadowColor = color.cgColor;// 阴影颜色
layer.shadowOpacity = opactiy;// 阴影透明度,默认0
layer.shadowOffset = .zero;//shadowOffset阴影偏移,默认(0, -3),这个跟shadowRadius配合使用
layer.shadowRadius = 3 //阴影半径,默认3
var shadowRect: CGRect?
switch type {
case .all:
shadowRect = CGRect.init(x: -shadowSize, y: -shadowSize, width: bounds.size.width + 2 * shadowSize, height: bounds.size.height + 2 * shadowSize)
case .top:
shadowRect = CGRect.init(x: -shadowSize, y: -shadowSize, width: bounds.size.width + 2 * shadowSize, height: 2 * shadowSize)
case .bottom:
shadowRect = CGRect.init(x: -shadowSize, y: bounds.size.height - shadowSize, width: bounds.size.width + 2 * shadowSize, height: 2 * shadowSize)
case .left:
shadowRect = CGRect.init(x: -shadowSize, y: -shadowSize, width: 2 * shadowSize, height: bounds.size.height + 2 * shadowSize)
case .right:
shadowRect = CGRect.init(x: bounds.size.width - shadowSize, y: -shadowSize, width: 2 * shadowSize, height: bounds.size.height + 2 * shadowSize)
}
layer.shadowPath = UIBezierPath.init(rect: shadowRect!).cgPath
}
///阴影 0.5
@objc public func bs_shadow(color: UIColor = UIColor(red: 0.87, green: 0.87, blue: 0.87, alpha: 0.7), offset: CGSize = CGSize(width: 0, height: 1.5), opacity: Float = 1, radius: CGFloat = 3.5) {
self.layer.shadowColor = color.cgColor
self.layer.shadowOffset = offset
self.layer.shadowOpacity = opacity
self.layer.shadowRadius = radius
}
///内阴影
@objc public func bs_innerShadow() {
self.layer.shadowColor = UIColor(red: 0.87, green: 0.87, blue: 0.87, alpha: 0.5).cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 0)
self.layer.shadowOpacity = 1
self.layer.shadowRadius = 3
}
}
// MARK: - 圆角设置
extension UIView {
public func setCorners(corners: UIRectCorner, radius: CGFloat) -> Void {
self.setCorners(corners: corners, radius: radius, rect: self.bounds)
}
public func setCorners(corners: UIRectCorner, radius: CGFloat, rect: CGRect) -> Void {
let path = UIBezierPath.init(roundedRect: rect, byRoundingCorners: corners , cornerRadii: CGSize.init(width: radius, height: radius))
let layer = CAShapeLayer.init()
layer.frame = self.bounds
layer.path = path.cgPath
self.layer.mask = layer
}
///渐变颜色(frame需要有值)
@objc public func bs_gradient(_ type: ShadowType = .left, colors: [UIColor], locations: [NSNumber]? = nil) {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
//设置颜色
var cgColors: [CGColor] = []
for color in colors {
cgColors.append(color.cgColor)
}
gradientLayer.colors = cgColors
//设置占比
if locations == nil {
let num = 1.0/(Double(colors.count)-1.0)
var locations: [NSNumber] = []
for i in 0..<colors.count {
locations.append(NSNumber(value: num*Double(i)))
}
gradientLayer.locations = locations
} else {
gradientLayer.locations = locations
}
//设置位置
switch type {
case .all:
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 0.5)
case .top:
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
break
case .left:
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
break
case .right:
gradientLayer.startPoint = CGPoint(x: 1, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0, y: 0.5)
break
case .bottom:
gradientLayer.startPoint = CGPoint(x: 0.5, y: 1)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 0)
break
}
self.layer.addSublayer(gradientLayer)
}
/// 绘制虚线
/// - Parameters:
/// - lineLength: 长度
/// - lineSpacing: 间隔长度
/// - lineColor: 颜色
public func bs_drawDashLine(lineLength: Int, lineSpacing: Int, lineColor: UIColor) {
let shapeLayer = CAShapeLayer()
shapeLayer.bounds = self.bounds
shapeLayer.anchorPoint = CGPoint(x: 0, y: 0)
shapeLayer.strokeColor = lineColor.cgColor
shapeLayer.lineWidth = 1 //描边路径时使用的线宽。默认为1。
shapeLayer.lineJoin = CAShapeLayerLineJoin.round
shapeLayer.lineDashPattern = [NSNumber(value: lineLength),NSNumber(value: lineSpacing)]
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: self.bounds.size.width, y: 0))
shapeLayer.path = path
self.layer.addSublayer(shapeLayer)
}
}
// MARK: - Frame
extension UIView {
//let cOrigin: CGPoint //!< 位置
//let cSize: CGSize //!< 大小
open var height: CGFloat {
get {
return self.frame.height
}
set {
var frame = self.frame
frame.size.height = newValue
self.frame = frame
}
} //!< 高度
open var width: CGFloat {
get {
return self.frame.width
}
set {
var frame = self.frame
frame.size.width = newValue
self.frame = frame
}
} //!< 宽度
open var top: CGFloat {
get {
return self.frame.minY
}
set {
var frame = self.frame
frame.origin.y = newValue
self.frame = frame
}
} //!< 上
open var left: CGFloat {
get {
return self.frame.minX
}
set {
var frame = self.frame
frame.origin.x = newValue
self.frame = frame
}
} //!< 左
open var bottom: CGFloat {
get {
return self.frame.maxY
}
set {
var frame = self.frame
frame.origin.y = newValue - self.frame.height
self.frame = frame
}
} //!< 下
open var right: CGFloat {
get {
return self.frame.maxX
}
set {
var frame = self.frame;
frame.origin.x = newValue - self.frame.width
self.frame = frame
}
} //!< 右
}
//
// UIViewController+GDNavigation.swift
// GDKit
//
// Created by SJ on 2022/1/15.
//
import UIKit
// MARK: - 获取当前控制器
extension UIViewController {
// 获取当前正呈现的UIViewController
static var current:UIViewController? {
let delegate = UIApplication.shared.delegate as? AppDelegate
var current = delegate?.window?.rootViewController
while (current?.presentedViewController != nil) {
current = current?.presentedViewController
}
if let tabbar = current as? UITabBarController , tabbar.selectedViewController != nil {
current = tabbar.selectedViewController
}
while let navi = current as? UINavigationController , navi.topViewController != nil {
current = navi.topViewController
}
return current
}
//获取UINavigationController的topViewController
static var navTopViewController: UIViewController? {
let delegate = UIApplication.shared.delegate as? AppDelegate
var current = delegate?.window?.rootViewController
if let tabbar = current as? UITabBarController , tabbar.selectedViewController != nil {
current = tabbar.selectedViewController
}
while let navi = current as? UINavigationController , navi.topViewController != nil {
current = navi.topViewController
}
return current
}
class func classFromString(_ className:String) -> UIViewController? {
//1、获swift中的命名空间名
var name = Bundle.main.object(forInfoDictionaryKey: "CFBundleExecutable") as? String
//2、如果包名中有'-'横线这样的字符,在拿到包名后,还需要把包名的'-'转换成'_'下横线
name = name?.replacingOccurrences(of: "-", with: "_")
//3、拼接命名空间和类名,”包名.类名“
let fullClassName = name! + "." + className
//通过NSClassFromString获取到最终的类,由于NSClassFromString返回的是AnyClass,这里要转换成UIViewController.Type类型
guard let classType = NSClassFromString(fullClassName) as? UIViewController.Type else {
//fatalError("转换失败")
return nil
}
return classType.init()
}
func bs_popToRootViewController(animated: Bool) {
let vcs = self.navigationController?.popToRootViewController(animated: true)
if vcs != nil {
self.tabBarController?.tabBar.isHidden = false
}
}
}
// MARK: - 创建RightBarButtonItem
extension UIViewController {
public func bs_setupRightButton(title: String? = nil, color: UIColor? = nil, image: UIImage? = nil, font: UIFont? = nil, imageColor: UIColor? = nil, reset: Bool = false) {
var button: UIButton?
if let rightBarButtonItems = self.navigationItem.rightBarButtonItems,
rightBarButtonItems.count > 0,
reset == false {
for view in rightBarButtonItems {
if let rightButton = view.customView as? UIButton {
button = rightButton
}
}
} else {
let rightButton = UIButton(type: .custom)
rightButton.frame = CGRect(x: 0, y: 0, width: 70, height: 44)
rightButton.addTarget(self, action: #selector(clickedNavRight), for: .touchUpInside)
rightButton.contentHorizontalAlignment = .right
let rightItem = UIBarButtonItem(customView: rightButton)
let negativeSeperator = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
negativeSeperator.width = -0
self.navigationItem.rightBarButtonItems = [negativeSeperator,rightItem]
button = rightButton
}
if font == nil {
button?.titleLabel?.font = UIFont.PFSCM(ofSize: 16)
} else {
button?.titleLabel?.font = font
}
if var image = image {
if let imageColor = imageColor {
image = image.withRenderingMode(.alwaysTemplate)
button?.tintColor = imageColor
} else {
image = image.withRenderingMode(.alwaysOriginal)
}
button?.setImage(image, for: .normal)
button?.setImage(image, for: .highlighted)
}
button?.setTitle(title, for: .normal)
button?.setTitleColor(color, for: .normal)
}
@objc func clickedNavRight() {
}
}
// MARK: - 配置导航栏
extension UIViewController {
public func bs_config() {
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.setNavigationBarHidden(false, animated: true)
self.bs_setupNavigationAttributed(.white)
self.bs_setupTitleAttributedAll()
self.bs_setupLeftButton()
self.bs_openPopGesture()
}
///设置导航条属性
func bs_setupNavigationAttributed(_ color: UIColor = UIColor.white) {
//设置状态栏
//UIApplication.shared.statusBarStyle = .default
//self.preferredStatusBarStyle = .default
self.navigationController?.navigationBar.barTintColor = color
self.navigationController?.navigationBar.setBackgroundImage(UIImage.from(color: color), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage() //隐藏下划线
}
///设置全局标题属性
func bs_setupTitleAttributedAll(font: UIFont = UIFont.PFSCM(ofSize: 16), color: UIColor = .black) {
let titleDict = [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: color]
self.navigationController?.navigationBar.titleTextAttributes = titleDict
}
///设置独立标题属性
func bs_setupTitleAttributed(title: String, font: UIFont = UIFont.PFSCM(ofSize: 16), color: UIColor = .black, reset: Bool = false) {
var titleLabel: UILabel?
if let label = self.navigationItem.titleView as? UILabel,
reset == false {
titleLabel = label
} else {
let label = UILabel()
titleLabel = label
}
titleLabel?.font = font
titleLabel?.textColor = color
titleLabel?.text = title
self.navigationItem.titleView = titleLabel
}
///返回按钮
func bs_setupLeftButton(title: String? = nil, color: UIColor? = nil, image: UIImage? = UIImage(named: "back_icon"), reset: Bool = false) {
var button: UIButton?
if let leftBarButtonItems = self.navigationItem.leftBarButtonItems,
leftBarButtonItems.count > 0,
reset == false {
for view in leftBarButtonItems {
if let leftButton = view.customView as? UIButton {
button = leftButton
}
}
} else {
let leftButton = UIButton(type: .custom)
leftButton.frame = CGRect(x: 0, y: 0, width: 44, height: 44)
leftButton.addTarget(self, action: #selector(callBackAction), for: .touchUpInside)
leftButton.contentHorizontalAlignment = .left
leftButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: -4, bottom: 0, right: 0)
let leftItem = UIBarButtonItem(customView: leftButton)
let negativeSeperator = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
negativeSeperator.width = -0
self.navigationItem.leftBarButtonItems = [negativeSeperator,leftItem]
button = leftButton
}
var image = image
if color != nil {
image = image?.withRenderingMode(.alwaysTemplate)
} else {
image = image?.withRenderingMode(.alwaysOriginal)
}
button?.setImage(image, for: .normal)
button?.setImage(image, for: .highlighted)
button?.titleLabel?.font = UIFont.PFSCM(ofSize: 16)
button?.setTitle(title, for: .normal)
button?.setTitleColor(color, for: .normal)
button?.tintColor = color
}
///返回按钮响应
@objc func callBackAction() {
let selector = NSSelectorFromString("onCallBackAction")
if self.responds(to: selector) == true {
self.performSelector(onMainThread: selector, with: nil, waitUntilDone: false)
} else {
self.navigationController?.popViewController(animated: true)
}
}
/**
打开系统手势返回
*/
@objc func bs_openPopGesture() {
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
}
/**
关闭系统手势返回
*/
@objc func bs_closePopGesture() {
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}
///修改导航栏背景色(全局)
public func bs_navBackgroundColor(_ color: UIColor) {
self.navigationController?.view.backgroundColor = color
//解决iOS13后backgroundColor设置无效的问题,参考https://developer.apple.com/forums/thread/682420
if #available(iOS 13.0, *) {
let newAppearance = UINavigationBarAppearance()
newAppearance.configureWithOpaqueBackground()
newAppearance.backgroundColor = color
newAppearance.shadowImage = UIImage()
newAppearance.shadowColor = nil
//newAppearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.red, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20, weight: .medium)]
let appearance = self.navigationController?.navigationBar ?? UINavigationBar.appearance()
appearance.standardAppearance = newAppearance
appearance.scrollEdgeAppearance = appearance.standardAppearance
} else {
bs_setupNavigationAttributed(color)
}
}
}
//
// BSjsonUtil.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/11/18.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
import HandyJSON
class BSJsonUtil: NSObject {
/**
* Json转对象
*/
static func jsonToModel(_ jsonStr:String,_ modelType:HandyJSON.Type) ->BsBaseModel {
if jsonStr == "" || jsonStr.count == 0 {
#if DEBUG
print("jsonoModel:字符串为空")
#endif
return BsBaseModel()
}
return modelType.deserialize(from: jsonStr) as! BsBaseModel
}
/**
* Json转数组对象
*/
static func jsonArrayToModel(_ jsonArrayStr:String, _ modelType:HandyJSON.Type) ->[BsBaseModel] {
if jsonArrayStr == "" || jsonArrayStr.count == 0 {
#if DEBUG
print("jsonToModelArray:字符串为空")
#endif
return []
}
var modelArray:[BsBaseModel] = []
let data = jsonArrayStr.data(using: String.Encoding.utf8)
let peoplesArray = try! JSONSerialization.jsonObject(with:data!, options: JSONSerialization.ReadingOptions()) as? [AnyObject]
for people in peoplesArray! {
modelArray.append(dictionaryToModel(people as! [String : Any], modelType))
}
return modelArray
}
/**
* 字典转对象
*/
static func dictionaryToModel(_ dictionStr:[String:Any],_ modelType:HandyJSON.Type) -> BsBaseModel {
if dictionStr.count == 0 {
#if DEBUG
print("dictionaryToModel:字符串为空")
#endif
return BsBaseModel()
}
return modelType.deserialize(from: dictionStr) as! BsBaseModel
}
/**
* 对象转JSON
*/
static func modelToJson(_ model:BsBaseModel?) -> String {
if model == nil {
#if DEBUG
print("modelToJson:model为空")
#endif
return ""
}
return (model?.toJSONString())!
}
/**
* 对象转字典
*/
static func modelToDictionary(_ model:BsBaseModel?) -> [String:Any] {
if model == nil {
#if DEBUG
print("modelToJson:model为空")
#endif
return [:]
}
return (model?.toJSON())!
}
//数组转json
static func arrayToJson(_ array:NSArray) -> String {
if (!JSONSerialization.isValidJSONObject(array)) {
print("无法解析出JSONString")
return ""
}
let data : NSData! = try? JSONSerialization.data(withJSONObject: array, options: []) as NSData
let JSONString = NSString(data:data as Data,encoding: String.Encoding.utf8.rawValue)
return JSONString! as String
}
/**
* 字典转JSON
*/
static func dictionaryToJson(_ dictionary:NSDictionary) -> String {
if (!JSONSerialization.isValidJSONObject(dictionary)) {
print("无法解析出JSONString")
return ""
}
let data : NSData! = try? JSONSerialization.data(withJSONObject: dictionary, options: []) as NSData
let JSONString = NSString(data:data as Data,encoding: String.Encoding.utf8.rawValue)
return JSONString! as String
}
}
//
// BsConstant.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/10/19.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
// MARK: - 工程中的 常量定义
class BsConstant {
// MARK: - 个人信息基本类型
enum UserBaseInfoType : Int {
case unknow //未知
case sex//性别
case birthday//生日
case height//身高
case weight//体重
case education //学历
case profession //职务
case work_domain//从事行业
case income//收入
func title() -> String {
switch self {
case .sex:
return "性别"
case .birthday:
return "生日"
case .height:
return "身高"
case .weight:
return "体重"
case .education:
return "学历"
case .profession:
return "职务"
case .work_domain:
return "从事行业"
case .income:
return "收入"
default:
return "--"
}
}
func titleForSelectView() -> String {
switch self {
case .sex:
return "性别"
case .birthday:
return "生日"
case .height:
return "身高(单位:cm)"
case .weight:
return "体重(单位:kg)"
case .education:
return "学历"
case .profession:
return "职务"
case .work_domain:
return "从事行业"
case .income:
return "收入(单位:k)"
default:
return "--"
}
}
/*
var id : String = ""//用户ID
var user_type : String = ""//用户类型 1:普通用户2.明星用户3.管理员
var sex : String = ""//性别 0其他, 1:男, 2女
var real_name : String = "" //真实姓名
var nick_name : String = "" //昵称
var avatar : String = ""//头像
var birthday : String = ""//生日
var status : String = ""//用户状态1:正常,0:已注销
var height : String = ""//身高
var weight : String = ""//体重
var user_name : String = ""//用户名
var phone : String = ""//手机号码
var marital_status : String = ""//婚姻情况
var profession : String = ""//职业ID
var profession_name : String = ""//职业名称
var education : String = ""//文化程度ID
var education_name : String = ""//文化程度名称
var work_domain : String = ""//行业领域ID
var work_domain_name : String = ""//行业领域名称
*/
func key() -> String {
switch self {
case .sex:
return "sex"
case .birthday:
return "birthday"
case .height:
return "height"
case .weight:
return "weight"
case .education:
return "education"
case .profession:
return "profession"
case .work_domain:
return "work_domain"
case .income:
return "monthly_income"
default:
return ""
}
}
}
// MARK: - URL 相关
struct URL {
// MARK: - 帮助与反馈
static let userQAUrl = YHBaseUrlManager.shared.h5URL() + "landing/index.html#/pages/my/index"
// MARK: - 优眠用户注销条款
static let userUnregisterURL = "https://www.baidu.com"
// MARK: - 隐私政策
static let privacyURL = YHBaseUrlManager.shared.h5URL() + "landing/index.html#/subpages/personal/pages/protocol/index?classify=privacy"
// MARK: - 用户协议
static let userProtocalURL = YHBaseUrlManager.shared.h5URL() + "landing/index.html#/subpages/personal/pages/protocol/index?classify=agreement"
// MARK: - 付费咨询服务协议
static let payProtocalURL = "https://www.baidu.com"
// MARK: - 官网地址
static let officialWebsite = "https://www.sumian.com"
// MARK: - 量表入口
static let liangBiaoEntryWebsite = YHBaseUrlManager.shared.h5URL() + "landing/index.html#/pages/medicineCenter/index"
// MARK: - 通用链接,可以在 Safari 等 web 环境打开 APP,目前主要是提供给微信APP调用
static let universal_link = "https://sdapi.sumian.com/sleepdoctor/"
static let wx_universal_link = universal_link + "wx-sign-in/"
//#if DEBUG
// // MARK: - 医学中心
// static let medical_center_url = "http://10.168.3.41:10086/h5/#/pages/medicineCenter/index"
//#else
// MARK: - 医学中心
static let medical_center_url = YHBaseUrlManager.shared.h5URL() + "landing/index.html#/pages/medicineCenter/index"
//#endif
// MARK: - 助眠专辑详情
//static let music_detail_url = BsBaseUrlManager.shared.h5URL() + "landing/index.html#/subpages/platform/pages/musicdetail/index?immerse=1"
// MARK: - 文章列表
//static let article_list_url = BsBaseUrlManager.shared.h5URL() + "landing/index.html#/subpages/platform/pages/articlelist/index?immerse=1"
// MARK: - 文章详情
static let article_detail_url = YHBaseUrlManager.shared.h5URL() + "landing/index.html#/subpages/platform/pages/articledetail/index?immerse=1"
// MARK: - 科普视频列表
//static let video_list_url = BsBaseUrlManager.shared.h5URL() + "landing/index.html#/subpages/platform/pages/videolist/index?immerse=1"
}
// MARK: - 阿里云 手机号一键登录 秘钥信息
static let kAliOnePressPhoneLoginKey = "pjYDHIVE1Q4qu7QJNI8MaxfvqJQvn5j+Az8xHRlnctj+kUAGf2Hin6ONsTFu1yjCwWPvhmw5NlhFo5DuP1SabykuX1Dz/xl4aDDLQQ5oijBNqRtwmwFAHl/exvfLk6zndWdv3Hi+DUL6gRwAj2UWj1rNXAMP2YSlisC0NSrFFFpiKyHAd2dM2OpIiGh9lkGeihU5iOjv/IycVMW6l2Sx9QqcrKiAX44aPMp0/uS5+y0xYwy/CjV6bvpacHNstxtVFd2Vgi7p454="
// MARK: - 登录信息
static let KLoginedInfoKey = "KLoginedInfoKey"
// MARK: - 上次登录成功后的 手机号码
static let KLastLoginedSuccessPhoneKey = "KLastLoginedSuccessPhoneKey"
// MARK: - 登录用户 详情信息
static let kDetailInfo = "kDetailInfo"
}
extension BsConstant {
// MARK: - 友盟相关
struct Wechat {
static let appKey = "wx57b2a7469d15ff8f"
static let AppSecret = "e0a351290c66c05f85c54a31b213c1aa"
}
struct UM {
static let appKey = "5a77b807a40fa30f0300008b"
}
// MARK: - 支付宝
struct Alipay {
static let scheme = "com.usleep.health.alipay"
}
// MARK: - 通知相关 名称
class BsNotification {
public static let didCancelTaskNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didCancelTask")
//用户详细信息更改成功
public static let didUpdateUserDetailInfoSuccessNotifiction = Notification.Name(rawValue: "com.usleep.health.updateUserDetailInfo.success")
//登录成功
public static let didLoginSuccessNotifiction = Notification.Name(rawValue: "com.usleep.health.login.success")
//退出成功
public static let didLoginoutSuccessNotifiction = Notification.Name(rawValue: "com.usleep.health.loginout.success")
//token 过期
public static let tokenInvalidateNotifiction = Notification.Name(rawValue: "com.usleep.health.token.invalidate")
//用户注册 在完成登录、修改密码后需要 填写标签
public static let needSelectTagsNotification = Notification.Name(rawValue: "com.usleep.health.selecte.tags")
//跳转安睡主页
public static let goAnSleepNotification = Notification.Name(rawValue: "com.usleep.health.go.An.sleep")
//跳转填写量表列表界面
public static let goFillScaleNotification = Notification.Name(rawValue: "com.usleep.health.go.Fill.Scale")
//支付结果回调
public static let payResultNotification = Notification.Name(rawValue: "com.usleep.health.pay.result")
//隐藏tabBar
public static let tabBarHideNotification = Notification.Name(rawValue: "com.usleep.health.tabBar.hide")
//显示tabBar
public static let tabBarShowNotification = Notification.Name(rawValue: "com.usleep.health.tabBar.show")
//安睡主页切换Tab
public static let sleepPageChangeTabNotification = Notification.Name(rawValue: "com.usleep.health.sleepPage.changeTab")
}
}
//
// BsHUD.swift
// PKHUD Demo
//
// Created by ahao on 2018/3/20.
// Copyright © 2018年 NSExceptional. All rights reserved.
//
import UIKit
import PKHUD
public enum BsHUDType {
case success(message: String?)
case error(message: String?)
case warning(message: String?)
case progress(message: String?)
case image(image: UIImage?, message: String?)
case rotatingImage(image: UIImage?, message: String?)
}
class BsHUD {
// MARK: Properties
public static var dimsBackground: Bool {
get { return PKHUD.sharedHUD.dimsBackground }
set { PKHUD.sharedHUD.dimsBackground = newValue }
}
public static var allowsInteraction: Bool {
get { return PKHUD.sharedHUD.userInteractionOnUnderlyingViewsEnabled }
set { PKHUD.sharedHUD.userInteractionOnUnderlyingViewsEnabled = newValue }
}
public static var isVisible: Bool { return PKHUD.sharedHUD.isVisible }
// MARK: Public methods, PKHUD based
public static func show(_ content: BsHUDType, onView view: UIView? = nil) {
PKHUD.sharedHUD.contentView = contentView(content)
PKHUD.sharedHUD.show(onView: view)
}
public static func hide(_ completion: ((Bool) -> Void)? = nil) {
PKHUD.sharedHUD.hide(animated: false, completion: completion)
}
public static func hide(animated: Bool, completion: ((Bool) -> Void)? = nil) {
PKHUD.sharedHUD.hide(animated: animated, completion: completion)
}
public static func hide(afterDelay delay: TimeInterval, completion: ((Bool) -> Void)? = nil) {
PKHUD.sharedHUD.hide(afterDelay: delay, completion: completion)
}
public static func hideFlashMessage() {
let keyWindow = UIApplication.shared.windows.first {$0.isKeyWindow}
if keyWindow != nil {
while(keyWindow!.viewWithTag(BsHUD.flashMessageTag) != nil){
keyWindow!.viewWithTag(BsHUD.flashMessageTag)?.removeFromSuperview()
}
}
}
// MARK: Public methods, HUD based
public static func flash(_ content: BsHUDType, onView view: UIView? = nil) {
BsHUD.flash(content, onView: view, delay: 1, completion: nil)
}
public static func flash(_ content: BsHUDType, onView view: UIView? = nil, delay: TimeInterval, completion: ((Bool) -> Void)? = nil) {
BsHUD.show(content, onView: view)
BsHUD.hide(afterDelay: delay, completion: completion)
}
// MARK: Private methods
static func contentView(_ content: BsHUDType) -> UIView {
let todoView: UIView
switch content {
case let .progress(message):
todoView = BsHUDProgressView(title: message)
case let .success(message):
todoView = BsHUDSuccessView(title: message)
case let .error(message):
todoView = BsHUDErrorView(title: message)
case let .warning(message):
todoView = BsHUDWariningView(title: message)
case let .rotatingImage(image, message):
todoView = BsHUDSquareBaseView(image: image, title: message)
case let .image(image, message):
todoView = BsHUDSquareBaseView(image: image, title: message)
}
return todoView
}
}
extension BsHUD{
static func setup(){
PKHUD.sharedHUD.effect = nil
// PKHUD.sharedHUD.gracePeriod = 0.5
}
}
extension BsHUD{
static let flashMessageTag = 61535
static func flash(message: String,
dismissAfter: TimeInterval = 1.5,
duration:TimeInterval = 0.5,
isRemoveBefore: Bool = true,
insets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 20, bottom: 120, right: 20),
contentInsets: UIEdgeInsets = UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10),
containerWindow: UIWindow? = nil,
walk:((UILabel)->Void)? = nil){
if isRemoveBefore{
hideFlashMessage()
}
if message.isEmpty{
return
}
let containerView = UIView()
containerView.backgroundColor = UIColor.black.withAlphaComponent(0.7)
containerView.layer.cornerRadius = 4
containerView.alpha = 0
containerView.tag = flashMessageTag
let label = UILabel()
label.text = message
label.numberOfLines = 5
label.textColor = UIColor.white
label.font = UIFont.systemFont(ofSize: 14)
containerView.addSubview(label)
walk?(label)
let keyW = UIApplication.shared.windows.first {$0.isKeyWindow}
if let keyWindow = (containerWindow ?? keyW){
let labelWidth = keyWindow.bounds.width-insets.left-insets.right-contentInsets.left-contentInsets.right
label.frame = CGRect(x: contentInsets.left,
y: contentInsets.top,
width: labelWidth,
height: 30)
label.sizeToFit()
let containerViewSize = CGSize(width: label.frame.width+contentInsets.left+contentInsets.right,
height: label.frame.height+contentInsets.top+contentInsets.bottom)
containerView.frame = CGRect(x: (keyWindow.bounds.width-containerViewSize.width)/2.0,
y: (keyWindow.bounds.height-containerViewSize.height)/2.0,
width: containerViewSize.width,
height: containerViewSize.height)
keyWindow.addSubview(containerView)
UIView.animate(withDuration: duration, animations: {
containerView.alpha = 1
}, completion: { (_) in
UIView.animate(withDuration: duration, delay: dismissAfter, options: [], animations: {
containerView.alpha = 0
}, completion: { (_) in
containerView.removeFromSuperview()
})
})
}
}
}
//
// BsHUDContainerView.swift
// PKHUD Demo
//
// Created by ahao on 2018/3/20.
// Copyright © 2018年 NSExceptional. All rights reserved.
//
import UIKit
class BsHUDContainerView: UIView {
static let defaultFrame = CGRect(origin: CGPoint.zero, size: CGSize(width: 156.0, height: 156.0))
private let offSet: CGFloat
var contentView: UIView
init(contentView:UIView,offSet: CGFloat = 0) {
self.contentView = contentView
self.offSet = offSet
super.init(frame: BsHUDContainerView.adjustFrame(contentViewFrame: contentView.frame, offSet: offSet))
addSubview(contentView)
}
func adjustFrame() -> CGRect{
return BsHUDContainerView.adjustFrame(contentViewFrame: contentView.frame, offSet: offSet)
}
class func adjustFrame(contentViewFrame: CGRect, offSet: CGFloat) -> CGRect{
let height = contentViewFrame.height + abs(offSet) * 2
let adjustSize = CGSize(width: contentViewFrame.width, height: height)
return CGRect(origin: CGPoint.zero, size: adjustSize)
}
override func layoutSubviews() {
super.layoutSubviews()
let contentSize = contentView.frame.size
contentView.frame = CGRect(origin: CGPoint.zero, size: contentSize)
contentView.center = CGPoint(x: bounds.width/2.0, y: bounds.height/2.0 + offSet)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//
// BsHUDErrorView.swift
// PKHUD Demo
//
// Created by ahao on 2018/3/20.
// Copyright © 2018年 NSExceptional. All rights reserved.
//
import UIKit
import PKHUD
class BsHUDErrorView: BsHUDSquareBaseView {
public init(title: String? = nil) {
super.init(image: UIImage(named: "float_icon_error"), title: title)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
//
// BsHUDProgressView.swift
// PKHUD Demo
//
// Created by ahao on 2018/4/19.
// Copyright © 2018年 NSExceptional. All rights reserved.
//
import UIKit
import PKHUD
class BsHUDProgressView: BsHUDSquareBaseView,PKHUDAnimating {
public init(title: String? = nil) {
super.init(image:UIImage(named:"float_icon_loading"), title: title)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
public func startAnimation() {
imageView.layer.add(PKHUDAnimation.discreteRotation, forKey: "progressAnimation")
}
public func stopAnimation() {
imageView.layer.removeAnimation(forKey: "progressAnimation")
}
}
//
// BsHUDRotatingImageView.swift
// PKHUD Demo
//
// Created by ahao on 2018/4/19.
// Copyright © 2018年 NSExceptional. All rights reserved.
//
import UIKit
import PKHUD
class BsHUDRotatingImageView: BsHUDSquareBaseView,PKHUDAnimating {
public func startAnimation() {
imageView.layer.add(PKHUDAnimation.discreteRotation, forKey: "progressAnimation")
}
public func stopAnimation() {
}
}
//
// BsHUDSquareBaseView.swift
// PKHUD Demo
//
// Created by ahao on 2018/4/19.
// Copyright © 2018年 NSExceptional. All rights reserved.
//
import UIKit
import PKHUD
class BsHUDSquareBaseView: UIView {
static let defaultSquareBaseViewFrame = CGRect(origin: CGPoint.zero, size: CGSize(width: 120, height: 120))
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init(image: UIImage? = nil, title: String? = nil) {
super.init(frame: BsHUDSquareBaseView.defaultSquareBaseViewFrame)
backgroundColor = UIColor.black
imageView.image = image
titleLabel.text = title
titleLabel.sizeToFit()
addSubview(containerView)
containerView.addSubview(imageView)
containerView.addSubview(titleLabel)
}
let imageView: UIImageView = {
let imageView = UIImageView()
imageView.clipsToBounds = true
imageView.contentMode = .scaleAspectFit
return imageView
}()
let titleLabel: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 14.0)
label.textColor = UIColor.white
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.25
return label
}()
let containerView: UIView = {
let temp = UIView()
return temp
}()
open override func layoutSubviews() {
super.layoutSubviews()
let viewWidth = bounds.size.width
let viewHeight = bounds.size.height
let labelHeight = titleLabel.bounds.height
let imageViewHeight: CGFloat = max(imageView.image?.size.height ?? 28, 30)
let padding: CGFloat = labelHeight > 0 ? 8 : 0
let containerWidth = viewWidth - 5 - 5
let containerHeight = imageViewHeight + padding + labelHeight
containerView.frame.size = CGSize(width: containerWidth, height: containerHeight)
containerView.center = CGPoint(x: viewWidth/2.0, y: viewHeight/2.0)
imageView.frame = CGRect(origin: CGPoint.zero,
size: CGSize(width: containerWidth, height: imageViewHeight))
titleLabel.frame = CGRect(origin: CGPoint(x: 0, y: imageView.frame.maxY + padding),
size: CGSize(width: viewWidth-8, height: titleLabel.frame.height))
}
}
//
// BsHUDSuccessView.swift
// PKHUD Demo
//
// Created by ahao on 2018/3/20.
// Copyright © 2018年 NSExceptional. All rights reserved.
//
import UIKit
import PKHUD
class BsHUDSuccessView: BsHUDSquareBaseView {
public init(title: String? = nil) {
super.init(image: UIImage(named:"float_icon_success"), title: title)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
//
// BsHUDWariningView.swift
// PKHUD Demo
//
// Created by ahao on 2018/4/19.
// Copyright © 2018年 NSExceptional. All rights reserved.
//
import UIKit
class BsHUDWariningView: BsHUDSquareBaseView {
public init(title: String? = nil) {
super.init(image: UIImage(named:"float_icon_caveat"), title: title)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
//
// BsKVOHelper.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/10/19.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
///解决重复添加;解决重复移除;解决未添加却移除
open class BsKVOHelper {
class Container: Equatable {
static func == (lhs: BsKVOHelper.Container, rhs: BsKVOHelper.Container) -> Bool {
guard let lObserver = lhs.observer,
let lObject = lhs.object,
let rObserver = rhs.observer,
let rObject = rhs.object else {
return false
}
if lObserver == rObserver, lObject == rObject{
return lhs.keyPath == rhs.keyPath
}
return false
}
var observer: Int?
var object: Int?
var keyPath: String
init(observer: Int?, object: Int?, keyPath: String) {
self.observer = observer
self.object = object
self.keyPath = keyPath
}
func clear(){
observer = nil
object = nil
}
}
private var containers: [Container] = []
public init(){
}
open func add(observer: NSObject,toObject object: NSObject,forKeyPath keyPath: String, options: NSKeyValueObservingOptions = [], context: UnsafeMutableRawPointer?){
let aContainer = Container(observer: observer.hashValue, object: object.hashValue, keyPath: keyPath)
if containers.contains(where: { (thisContainer) -> Bool in
return aContainer == thisContainer
}){
return
}
containers.append(aContainer)
object.addObserver(observer,forKeyPath: keyPath, options: options, context: context)
}
open func remove(observer: NSObject,atObject object: NSObject,forKeyPath keyPath: String){
let aContainer = Container(observer: observer.hashValue, object: object.hashValue, keyPath: keyPath)
if let first = containers.first(where: { (thisContainer) -> Bool in
return aContainer == thisContainer
}){
object.removeObserver(observer, forKeyPath: keyPath)
first.clear()
}
}
}
//
// BsMapManager.swift
// BaiSiSMApp
//
// Created by DAVID on 2022/12/15.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
import MapKit
class BsMapManager {
// MARK: - 属性
static let sharedInstance = BsMapManager()
public let hasQqMap: Bool = UIApplication.shared.canOpenURL(URL(string: "qqmap://")!)
public let hasIosaMap: Bool = UIApplication.shared.canOpenURL(URL(string: "iosamap://")!)
public let hasBaiduMap: Bool = UIApplication.shared.canOpenURL(URL(string: "baidumap://")!)
// 当前位置或起始位置
var currentLocation: CLLocationCoordinate2D!
var currentAddress: String = ""
// 目标位置
var targetLocation: CLLocationCoordinate2D!
var targetAddress: String = ""
// MARK: - 导航位置初始化
func initNavi(currentLoc: CLLocationCoordinate2D! = nil, currentAdd: String = "", targetLoc: CLLocationCoordinate2D!, targetAdd: String) {
if currentLoc != nil {
currentLocation = currentLoc
}
currentAddress = currentAdd
if targetLoc != nil {
targetLocation = targetLoc
}
targetAddress = targetAdd
}
// MARK: - Apple地图导航
func naviWithAppleMap() {
if targetLocation == nil {
return
}
// 起始位置
let fromLocation = MKMapItem.forCurrentLocation()
fromLocation.name = "我的位置"
// 目标位置
let toCoor = CLLocationCoordinate2D(latitude: targetLocation!.latitude, longitude: targetLocation!.longitude)
let toLocation = MKMapItem(placemark: MKPlacemark(coordinate: toCoor, addressDictionary: [:]))
toLocation.name = targetAddress
// 打开地图
MKMapItem.openMaps(with: [fromLocation, toLocation],
launchOptions: [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving, MKLaunchOptionsShowsTrafficKey: NSNumber(true)])
}
// MARK: - 百度地图导航
func naviWithBaiduMap() {
if targetLocation == nil {
return
}
/// 请求格式:
/// baidumap://map/direction?origin=34.264642646862,108.95108518068&destination=40.007623,116.360582&coord_type=bd09ll&mode=driving&src=ios.baidu.openAPIdemo
var urlString: String = "baidumap://map/direction?"
// 起始位置
let origin: String = "我的位置"
urlString = urlString + "origin=\(origin)"
// 目标位置
let destination: String = "\(targetLocation!.latitude),\(targetLocation!.longitude)"
let name: String = targetAddress
urlString = urlString + "&destination=name:\(name)|latlng:\(destination)"
/// 可选的坐标系统:
/// bd09ll(百度经纬度坐标)
/// bd09mc(百度墨卡托坐标)
/// gcj02(经国测局加密的坐标)
/// wgs84(gps获取的原始坐标)
urlString = urlString + "&coord_type=gcj02&mode=driving&src=ios.ftsafe.FTSmartSudentCard"
urlString = urlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!
if #available(iOS 10.0, *) {
UIApplication.shared.open(URL(string: urlString)!, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(URL(string: urlString)!)
}
}
// MARK: - 腾讯地图导航
func naviWithQqMap() {
if targetLocation == nil {
return
}
/// 请求格式:
/// qqmap://map/routeplan?type=drive&from=清华&fromcoord=39.994745,116.247282&to=怡和世家&tocoord=39.867192,116.493187&referer=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77
/// 这里可能需要开发者key,即referer
var urlString: String = "qqmap://map/routeplan?type=drive"
let origin: String = "我的位置"
urlString = urlString + "&from=\(origin)&fromcoord=CurrentLocation"
let destination: String = "\(targetLocation!.latitude),\(targetLocation!.longitude)"
let name: String = targetAddress
urlString = urlString + "&to=\(name)&tocoord=\(destination)"
urlString = urlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!
if #available(iOS 10.0, *) {
UIApplication.shared.open(URL(string: urlString)!, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(URL(string: urlString)!)
}
}
// MARK: - 高德地图导航
func naviWithAMap() {
if targetLocation == nil {
return
}
/// 请求格式:
/// iosamap://path?sourceApplication=applicationName&sid=&slat=39.92848272&slon=116.39560823&sname=A&did=&dlat=39.98848272&dlon=116.47560823&dname=B&dev=0&t=0
var urlString: String = "iosamap://path?sourceApplication=FTSmartSudentCard"
let name: String = targetAddress
urlString = urlString + "&dlat=\(targetLocation!.latitude)&dlon=\(targetLocation!.longitude)&dname=\(name)&dev=0&t=0"
urlString = urlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!
if #available(iOS 10.0, *) {
UIApplication.shared.open(URL(string: urlString)!, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(URL(string: urlString)!)
}
}
}
//
// BsPageContentView.swift
// GDKit
//
// Created by GDKit on 01/11/2022.
// Copyright (c) 2022 GDKit. All rights reserved.
//
import UIKit
@objc public protocol BsPageContentViewDelegate : AnyObject {
func pageContentView(_ contentView : BsPageContentView, progress : CGFloat, sourceIndex : Int, targetIndex : Int)
}
private let ContentCellID = "BsContentCellID"
open class BsPageContentView: UIView {
// MARK: - 定义属性
fileprivate var childVcs : [UIViewController]
fileprivate weak var parentVC : UIViewController?
fileprivate var startOffsetX : CGFloat = 0
fileprivate var isForbidScrollDelegate : Bool = false
@objc public weak var delegate : BsPageContentViewDelegate?
public var isScrollEnabled: Bool? {
didSet {
if let isScrollEnabled = isScrollEnabled {
collectionView.isScrollEnabled = isScrollEnabled
}
}
}
@objc public func banScrollEnabled() {
collectionView.isScrollEnabled = false
}
fileprivate func getLayout() -> UICollectionViewFlowLayout {
let layout = UICollectionViewFlowLayout()
layout.itemSize = self.bounds.size
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.scrollDirection = .horizontal
return layout
}
// MARK: - 懒加载属性
fileprivate lazy var collectionView : UICollectionView = {[weak self] in
// 1.创建layout
let layout = self?.getLayout() ?? UICollectionViewFlowLayout()
// 2.创建UICollectionView
let collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
collectionView.backgroundColor = .clear//groupTableViewBackground
collectionView.showsHorizontalScrollIndicator = false
collectionView.isPagingEnabled = true
collectionView.bounces = false
collectionView.dataSource = self
collectionView.delegate = self
collectionView.scrollsToTop = false
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: ContentCellID)
return collectionView
}()
// MARK: - 自定义构造函数
@objc public init(frame: CGRect, childVcs : [UIViewController], parentViewController : UIViewController?) {
self.childVcs = childVcs
self.parentVC = parentViewController
super.init(frame: frame)
// 设置UI
setupUI()
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - 设置UI界面
extension BsPageContentView {
fileprivate func setupUI() {
// 1.将所有的子控制器添加父控制器中
for childVc in childVcs {
parentVC?.addChild(childVc)
}
// 2.添加UICollectionView,用于在Cell中存放控制器的View
addSubview(collectionView)
collectionView.frame = bounds
}
public func refreshRect() {
collectionView.frame = bounds
collectionView.collectionViewLayout = self.getLayout()
}
}
// MARK: - 遵守UICollectionViewDataSource
extension BsPageContentView : UICollectionViewDataSource {
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return childVcs.count
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// 1.创建Cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ContentCellID, for: indexPath)
// 2.给Cell设置内容
for view in cell.contentView.subviews {
view.removeFromSuperview()
}
let childVc = childVcs[(indexPath as NSIndexPath).item]
childVc.view.frame = cell.contentView.bounds
cell.contentView.addSubview(childVc.view)
return cell
}
}
// MARK: - 遵守UICollectionViewDelegate
extension BsPageContentView : UICollectionViewDelegate {
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
isForbidScrollDelegate = false
startOffsetX = scrollView.contentOffset.x
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
// 0.判断是否是点击事件
if isForbidScrollDelegate { return }
// 1.定义获取需要的数据
var progress : CGFloat = 0
var sourceIndex : Int = 0
var targetIndex : Int = 0
// 2.判断是左滑还是右滑
let currentOffsetX = scrollView.contentOffset.x
let scrollViewW = scrollView.bounds.width
if currentOffsetX > startOffsetX { // 左滑
// 1.计算progress
progress = currentOffsetX / scrollViewW - floor(currentOffsetX / scrollViewW)
// 2.计算sourceIndex
sourceIndex = Int(currentOffsetX / scrollViewW)
// 3.计算targetIndex
targetIndex = sourceIndex + 1
if targetIndex >= childVcs.count {
targetIndex = childVcs.count - 1
}
// 4.如果完全划过去
if currentOffsetX - startOffsetX == scrollViewW {
progress = 1
targetIndex = sourceIndex
}
} else { // 右滑
// 1.计算progress
progress = 1 - (currentOffsetX / scrollViewW - floor(currentOffsetX / scrollViewW))
// 2.计算targetIndex
targetIndex = Int(currentOffsetX / scrollViewW)
// 3.计算sourceIndex
sourceIndex = targetIndex + 1
if sourceIndex >= childVcs.count {
sourceIndex = childVcs.count - 1
}
}
// 3.将progress/sourceIndex/targetIndex传递给titleView
delegate?.pageContentView(self, progress: progress, sourceIndex: sourceIndex, targetIndex: targetIndex)
}
}
// MARK: - 对外暴露的方法
extension BsPageContentView {
@objc public func setCurrentIndex(_ currentIndex : Int) {
// 1.记录需要进制执行代理方法
isForbidScrollDelegate = true
// 2.滚动正确的位置
let offsetX = CGFloat(currentIndex) * collectionView.frame.width
collectionView.setContentOffset(CGPoint(x: offsetX, y: 0), animated: false)
}
}
//
// BsPageTitleView.swift
// GDKit
//
// Created by GDKit on 01/11/2022.
// Copyright (c) 2022 GDKit. All rights reserved.
//
import UIKit
// MARK: - 定义协议
@objc public protocol BsPageTitleViewDelegate : AnyObject {
/// 点击标题回调
///
/// - Parameters:
/// - titleView: BsPageTitleView
/// - index: 标题tag
func classTitleView(_ titleView : BsPageTitleView, selectedIndex index : Int)
func classTitleViewCurrentIndex(_ titleView : BsPageTitleView, currentIndex index : Int)
}
/// 滚动条高度
private let kScrollLineH : CGFloat = 2
/// 默认颜色
private let kNormalColor : (CGFloat, CGFloat, CGFloat) = (102, 102, 102)
/// 选中颜色
private let kSelectColor : (CGFloat, CGFloat, CGFloat) = (51, 51, 51)
/// 滚动条颜色
private let kBottomLineColor : (CGFloat, CGFloat, CGFloat) = (240, 240, 240)
private let kWhiteColor : (CGFloat, CGFloat, CGFloat) = (255, 255, 255)
open class BsPageTitleView: UIView {
fileprivate(set) var currentIndex : Int = 0
fileprivate var titles : [String]
@objc public weak var delegate : BsPageTitleViewDelegate?
fileprivate lazy var titleLabels : [UILabel] = [UILabel]()
fileprivate lazy var scrollView : UIScrollView = {
let scrollView = UIScrollView()
scrollView.showsHorizontalScrollIndicator = false
scrollView.scrollsToTop = false
// scrollView.bounces = false
return scrollView
}()
fileprivate var lineWidth:CGFloat
fileprivate var lineHeight:CGFloat
fileprivate var lineColor:UIColor?
fileprivate var lineBottomSpace:CGFloat
fileprivate var font:UIFont
fileprivate lazy var scrollLine : UIView = {
let scrollLine = UIView()
if let lineColor = self.lineColor {
scrollLine.backgroundColor = lineColor
} else {
scrollLine.backgroundColor = UIColor(r: self.titleNormalColor.0, g: self.titleNormalColor.1, b: self.titleNormalColor.2)
}
scrollLine.layer.cornerRadius = self.lineHeight/2
scrollLine.layer.masksToBounds = true
return scrollLine
}()
fileprivate lazy var backView : UIView = {
let view = UIView(frame: self.scrollView.bounds)
self.scrollView.addSubview(view)
return view
}()
var cellWidth:CGFloat = 0
var cellpadding:CGFloat = 0
var cellSpace:CGFloat = 20
fileprivate var scale:CGFloat = 0
/// 默认颜色
private var titleNormalColor : (CGFloat, CGFloat, CGFloat) = kNormalColor
/// 选中颜色
private var titleSelectColor : (CGFloat, CGFloat, CGFloat) = kSelectColor
/// 默认背景颜色
private var normalBackgroundColor : (CGFloat, CGFloat, CGFloat) = kWhiteColor
/// 选中背景颜色
private var selectBackgroundColor : (CGFloat, CGFloat, CGFloat) = kWhiteColor
/// 标题选中字形
private var titleSelectFont : UIFont = UIFont.boldSystemFont(ofSize: 14)
///是否隐藏底部分离线
private var isHiddenSeparator: Bool = false
///scrollView是否居中显示
private var isCenter: Bool = false
private var itemCornerRadius: CGFloat = 0 ///item圆角
/// 初始化
/// - Parameters:
/// - frame: CGRect
/// - titles: 标题
/// - lineWidth: 滚动条宽度
/// - lineHeight: 滚动条高度
/// - lineBottomSpace: 滚动条底部间隔
/// - lineColor: 滚动条颜色
/// - font: 字体
/// - cellWidth: cell宽度
/// - cellSpace: cell间隔
/// - scale: 缩放比例
/// - titleNormalColor: 标题默认颜色
/// - titleSelectColor: 标题选中颜色
/// - normalBackgroundColor: 标题默认背景颜色
/// - selectBackgroundColor: 标题选中背景颜色
/// - currentIndex: 当前选中下标
/// - isHiddenSeparator: 是否隐藏底部分割线
/// - titleSelectFont: 标题选中字形
/// - isCenter: 是否居中
/// - itemCornerRadius: item圆角
@objc public init(frame: CGRect,
titles:[String],
lineWidth: CGFloat,
lineHeight: CGFloat,
lineBottomSpace: CGFloat,
lineColor: UIColor?,
font: UIFont?,
cellWidth:CGFloat,
cellpadding:CGFloat,
cellSpace:CGFloat,
scale:CGFloat,
titleNormalColor:[CGFloat]?,
titleSelectColor:[CGFloat]?,
normalBackgroundColor:[CGFloat]?,
selectBackgroundColor:[CGFloat]?,
currentIndex:Int = 0,
isHiddenSeparator: Bool = false,
titleSelectFont: UIFont? = nil,
isCenter: Bool = false,
itemCornerRadius: CGFloat = 0) {
self.titles = titles
self.lineWidth = lineWidth // == 0 ? 50 : lineWidth
self.lineHeight = lineHeight // == 0 ? 2 : lineHeight
self.lineBottomSpace = lineBottomSpace
self.lineColor = lineColor
self.font = font == nil ? UIFont.boldSystemFont(ofSize: 14) : font!
self.cellWidth = cellWidth
self.cellpadding = cellpadding
self.cellSpace = cellSpace // == 0 ? 20 : cellSpace
self.scale = scale
if let titleNormalColor = titleNormalColor,
titleNormalColor.count == 3 {
self.titleNormalColor = (titleNormalColor[0],titleNormalColor[1],titleNormalColor[2])
}else{
self.titleNormalColor = kNormalColor
}
if let titleSelectColor = titleSelectColor,
titleSelectColor.count == 3 {
self.titleSelectColor = (titleSelectColor[0],titleSelectColor[1],titleSelectColor[2])
}else{
self.titleSelectColor = kSelectColor
}
if let normalBackgroundColor = normalBackgroundColor,
normalBackgroundColor.count == 3 {
self.normalBackgroundColor = (normalBackgroundColor[0],normalBackgroundColor[1],normalBackgroundColor[2])
}else{
self.normalBackgroundColor = kWhiteColor
}
if let selectBackgroundColor = selectBackgroundColor,
selectBackgroundColor.count == 3 {
self.selectBackgroundColor = (selectBackgroundColor[0],selectBackgroundColor[1],selectBackgroundColor[2])
}else{
self.selectBackgroundColor = kWhiteColor
}
self.isHiddenSeparator = isHiddenSeparator
self.isCenter = isCenter
self.itemCornerRadius = itemCornerRadius
if titles.count > currentIndex && currentIndex >= 0 {
self.currentIndex = currentIndex
}
if let titleSelectFont = titleSelectFont {
self.titleSelectFont = titleSelectFont
} else {
self.titleSelectFont = self.font
}
super.init(frame: frame)
// 设置UI界面
setupUI()
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func setup() {
scrollLine.isHidden = true
}
public func setScrollLine(isHidden: Bool) {
scrollLine.isHidden = isHidden
}
public func isScrollEnabled(_ isScrollEnabled: Bool) {
scrollView.isScrollEnabled = isScrollEnabled
}
public func setNone() {
self.titleLabelClick(UITapGestureRecognizer())
}
var tapGesList = [UITapGestureRecognizer]()
@objc public func moveToLast() {
if let tapGes = tapGesList.last {
titleLabelClick(tapGes)
}
}
@objc public func changedTitle(title: String, index: Int) {
if titleLabels.count <= index {
return
}
let label: UILabel = titleLabels[index]
label.text = title;
}
}
extension BsPageTitleView {
fileprivate func setupUI() {
// 1.添加UIScrollView
addSubview(scrollView)
scrollView.frame = bounds
// 2.添加title对应的Label
setupTitleLabels()
// 3.设置底线和滚动的滑块
setupBottomLineAndScrollLine()
}
fileprivate func setupTitleLabels(){
var contentWidth:CGFloat = 0
// 0.确定label的一些frame的值
// let labelW : CGFloat = frame.width / CGFloat(titles.count > count ? count : titles.count)
// let labelW : CGFloat = 120
let labelH : CGFloat = frame.height - lineHeight;
let labelY : CGFloat = 0
var labelX : CGFloat = cellSpace
for (index, title) in titles.enumerated() {
let label = UILabel()
label.text = title
label.tag = index
label.font = font
label.numberOfLines = 2
label.textColor = UIColor(r: self.titleNormalColor.0, g: self.titleNormalColor.1, b: self.titleNormalColor.2)
label.backgroundColor = UIColor(r: self.normalBackgroundColor.0, g: self.normalBackgroundColor.1, b: self.normalBackgroundColor.2)
label.textAlignment = .center
label.sizeToFit()
var width = cellWidth == 0 ? label.bounds.width : cellWidth
width += cellpadding * 2
label.frame = CGRect(x: labelX, y: labelY, width: width, height: labelH)
backView.addSubview(label)
labelX = label.frame.maxX + cellSpace
contentWidth = labelX
titleLabels.append(label)
if itemCornerRadius > 0 {
label.layer.cornerRadius = itemCornerRadius
label.layer.masksToBounds = true
}
// 5.给Label添加手势
label.isUserInteractionEnabled = true
let tapGes = UITapGestureRecognizer(target: self, action: #selector(self.titleLabelClick(_:)))
label.addGestureRecognizer(tapGes)
tapGesList.append(tapGes)
}
var backViewFrame = backView.frame
backViewFrame.size.width = contentWidth
backView.frame = backViewFrame
scrollView.contentSize = CGSize(width: contentWidth, height: bounds.height)
if self.bounds.size.width > contentWidth && isCenter == true {
backView.bounds.origin.x = (self.bounds.size.width - contentWidth)/2
}
}
fileprivate func setupBottomLineAndScrollLine() {
if isHiddenSeparator == false {
let bottomLine = UIView()
bottomLine.backgroundColor = UIColor(r: kBottomLineColor.0, g: kBottomLineColor.1, b: kBottomLineColor.2)
let lineH : CGFloat = 0.5
bottomLine.frame = CGRect(x: 0, y: frame.height - lineH, width: frame.width, height: lineH)
addSubview(bottomLine)
}
// 2.添加scrollLine
// 2.1.获取第一个Label
guard self.currentIndex < titleLabels.count else {
return
}
let firstLabel = titleLabels[self.currentIndex]
// guard let firstLabel = titleLabels.first else { return }
firstLabel.font = titleSelectFont
firstLabel.textColor = UIColor(r: self.titleSelectColor.0, g: self.titleSelectColor.1, b: self.titleSelectColor.2)
firstLabel.backgroundColor = UIColor(r: self.selectBackgroundColor.0, g: self.selectBackgroundColor.1, b: self.selectBackgroundColor.2)
firstLabel.transform = CGAffineTransform(scaleX: 1 + scale, y: 1 + scale)
// 2.2.设置scrollLine的属性
backView.addSubview(scrollLine)
scrollLine.frame = CGRect(x: firstLabel.frame.origin.x, y: frame.height - lineHeight - lineBottomSpace, width: lineWidth, height: lineHeight)
// scrollLine.frame = CGRect(x: firstLabel.frame.origin.x, y: frame.height - lineH, width: firstLabel.frame.width/2.5, height: lineH)
scrollLine.center.x = firstLabel.center.x
}
}
// MARK: - 监听Label的点击
extension BsPageTitleView {
@objc fileprivate func titleLabelClick(_ tapGes : UITapGestureRecognizer) {
// 0.获取当前Label
let currentLabel = tapGes.view as? UILabel ?? UILabel()
// 2.获取之前的Label
let oldLabel = titleLabels[currentIndex]
// 1.如果是重复点击同一个Title,那么直接返回
if currentLabel.tag != currentIndex {
currentLabel.font = titleSelectFont
oldLabel.font = font
// 3.切换文字的颜色
currentLabel.textColor = UIColor(r: self.titleSelectColor.0, g: self.titleSelectColor.1, b: self.titleSelectColor.2)
currentLabel.backgroundColor = UIColor(r: self.selectBackgroundColor.0, g: self.selectBackgroundColor.1, b: self.selectBackgroundColor.2)
oldLabel.textColor = UIColor(r: self.titleNormalColor.0, g: self.titleNormalColor.1, b: self.titleNormalColor.2)
oldLabel.backgroundColor = UIColor(r: self.normalBackgroundColor.0, g: self.normalBackgroundColor.1, b: self.normalBackgroundColor.2)
}
// 4.保存最新Label的下标值
currentIndex = currentLabel.tag
// 5.滚动条位置发生改变
UIView.animate(withDuration: 0.15, animations: {
self.scrollLine.center.x = currentLabel.center.x
})
let moveTotalX = currentLabel.center.x - oldLabel.center.x
if moveTotalX >= 0 {
if (currentLabel.frame.origin.x + currentLabel.bounds.size.width) > (self.scrollView.contentOffset.x + UIScreen.main.bounds.size.width) {
// 2.滚动正确的位置
let offsetX = currentLabel.frame.origin.x + currentLabel.bounds.size.width - UIScreen.main.bounds.size.width + 30
self.scrollView.setContentOffset(CGPoint(x: offsetX, y: 0), animated: true)
}
}else {
if currentIndex == 0 {
self.scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
}else {
if currentLabel.frame.origin.x < self.scrollView.contentOffset.x {
// 2.滚动正确的位置
let offsetX = currentLabel.frame.origin.x - 10
self.scrollView.setContentOffset(CGPoint(x: offsetX, y: 0), animated: true)
}
}
}
delegate?.classTitleView(self, selectedIndex: currentIndex)
}
}
// MARK: - 对外暴露的方法
extension BsPageTitleView {
@objc public func setTitleWithProgress(_ progress : CGFloat, sourceIndex : Int, targetIndex : Int) {
let maxScale = 1 + scale
// 1.取出sourceLabel/targetLabel
let sourceLabel = titleLabels[sourceIndex]
let targetLabel = titleLabels[targetIndex]
// 2.处理滑块的逻辑
let moveTotalX = targetLabel.center.x - sourceLabel.center.x
let moveX = moveTotalX * progress
scrollLine.center.x = sourceLabel.center.x + moveX
// 3.颜色的渐变(复杂)
// 3.1.取出变化的范围
let colorDelta = (self.titleSelectColor.0 - self.titleNormalColor.0, self.titleSelectColor.1 - self.titleNormalColor.1, self.titleSelectColor.2 - self.titleNormalColor.2)
// 3.2.变化sourceLabel
sourceLabel.textColor = UIColor(r: self.titleSelectColor.0 - colorDelta.0 * progress, g: self.titleSelectColor.1 - colorDelta.1 * progress, b: self.titleSelectColor.2 - colorDelta.2 * progress)
// 3.2.变化targetLabel
targetLabel.textColor = UIColor(r: self.titleNormalColor.0 + colorDelta.0 * progress, g: self.titleNormalColor.1 + colorDelta.1 * progress, b: self.titleNormalColor.2 + colorDelta.2 * progress)
//背景颜色渐变
let backColorDelta = (self.selectBackgroundColor.0 - self.normalBackgroundColor.0, self.selectBackgroundColor.1 - self.normalBackgroundColor.1, self.selectBackgroundColor.2 - self.normalBackgroundColor.2)
sourceLabel.backgroundColor = UIColor(r: self.selectBackgroundColor.0 - backColorDelta.0 * progress, g: self.selectBackgroundColor.1 - backColorDelta.1 * progress, b: self.selectBackgroundColor.2 - backColorDelta.2 * progress)
targetLabel.backgroundColor = UIColor(r: self.normalBackgroundColor.0 + backColorDelta.0 * progress, g: self.normalBackgroundColor.1 + backColorDelta.1 * progress, b: self.normalBackgroundColor.2 + backColorDelta.2 * progress)
// sourceLabel缩放
sourceLabel.transform = CGAffineTransform(scaleX: maxScale - scale * progress, y:maxScale - scale * progress)
// targetLabel缩放
targetLabel.transform = CGAffineTransform(scaleX: 1 + scale * progress, y:1 + scale * progress)
if progress == 1.0 {
// 4.记录最新的index
currentIndex = targetIndex
delegate?.classTitleViewCurrentIndex(self, currentIndex: currentIndex)
if moveTotalX >= 0 {
if (targetLabel.frame.origin.x + targetLabel.bounds.size.width) > (self.scrollView.contentOffset.x + UIScreen.main.bounds.size.width) {
// 2.滚动正确的位置
let offsetX = targetLabel.frame.origin.x + targetLabel.bounds.size.width - UIScreen.main.bounds.size.width + 30
self.scrollView.setContentOffset(CGPoint(x: offsetX, y: 0), animated: true)
}
}else {
if currentIndex == 0 {
self.scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
}else {
if targetLabel.frame.origin.x < self.scrollView.contentOffset.x {
// 2.滚动正确的位置
let offsetX = targetLabel.frame.origin.x - 10
self.scrollView.setContentOffset(CGPoint(x: offsetX, y: 0), animated: true)
}
}
}
}
}
}
//
// BsPresentView.swift
// BaiSiSMApp
//
// Created by DAVID on 2022/12/15.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
/// view.tag = 98 由下往上推出
/// view.tag = 99 由frame起点缩放
open class BsPresentView: UIView {
var animated: Bool = true
///由下往上推出
@objc public func bs_showView(_ view: UIView, isTapEnabled: Bool = true) {
self.animated = true
bs_showView(view, isTapEnabled: isTapEnabled, animated: true)
}
///由下往上推出
@objc public func bs_showView(_ view: UIView, isTapEnabled: Bool = true, animated: Bool) {
// self.keyWindow = [[[UIApplication sharedApplication] delegate] window];
// self.keyVC = self.keyWindow.rootViewController;
// self.frame = self.keyWindow.bounds;
view.tag = 98
self.animated = animated
self.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height)
self.backgroundColor = UIColor.black.withAlphaComponent(0.4)
UIApplication.shared.keyWindow?.addSubview(self)
let topView = UIView(frame: self.frame)
self.addSubview(topView)
topView.height = self.height - view.height
if isTapEnabled == true {
let tap = UITapGestureRecognizer(target: self, action: #selector(bs_closeView))
topView.addGestureRecognizer(tap)
}
self.addSubview(view)
view.top = self.height
if (animated == true) {
UIView.animate(withDuration: 0.25) {
view.top = self.height - view.height
}
}else{
view.top = self.height - view.height
}
}
///由frame为起点缩放
@objc public func bs_showView(_ view: UIView, frame: CGRect) {
view.tag = 99
self.animated = true
//view.frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: 0, height: 0)
self.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height)
//self.backgroundColor = UIColor.black.withAlphaComponent(0.4)
UIApplication.shared.keyWindow?.addSubview(self)
let backView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height))
self.addSubview(backView)
let tap = UITapGestureRecognizer(target: self, action: #selector(bs_closeView))
backView.addGestureRecognizer(tap)
backView.isUserInteractionEnabled = false
self.addSubview(view)
view.frame = frame
if (animated == true) {
var offsetX = frame.size.width / 2
if frame.origin.x < (UIScreen.main.bounds.size.width - frame.size.width)/2 {
offsetX = -frame.size.width / 2
}
let offsetY = -frame.size.height / 2
view.transform = __CGAffineTransformMake(0.01, 0, 0, 0.01, offsetX, offsetY)
//view.layer.anchorPoint = CGPoint(x: 0, y: 0) //锚点
UIView.animate(withDuration: 0.3) {
view.alpha = 1.0
view.transform = __CGAffineTransformMake(1.05, 0, 0, 1.0, 0, 0)
} completion: { success in
UIView.animate(withDuration: 0.1) {
view.transform = __CGAffineTransformMake(1, 0, 0, 1, 0, 0)
} completion: { success in
// 恢复原位
view.transform = CGAffineTransform.identity
backView.isUserInteractionEnabled = true
}
}
}else{
view.frame = frame
}
}
// override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// self.bs_closeView()
// }
@objc public func bs_closeView() {
guard let view = self.subviews.last, self.subviews.count == 2 else {
self.removeFromSuperview()
return
}
if (animated == true) {
if view.tag == 99 {
let frame = view.frame
// 动画由大变小
view.transform = __CGAffineTransformMake(1, 0, 0, 1, 0, 0)
UIView.animate(withDuration: 0.2) {
var offsetX = frame.size.width / 2
if frame.origin.x < (UIScreen.main.bounds.size.width - frame.size.width)/2 {
offsetX = -frame.size.width / 2
}
let offsetY = -frame.size.height / 2
view.transform = __CGAffineTransformMake(0.01, 0, 0, 0.01, offsetX, offsetY)
} completion: { success in
view.transform = CGAffineTransform.identity
view.alpha = 0.0
self.removeFromSuperview()
}
} else {
UIView.animate(withDuration: 0.25, animations: {
view.top = self.height
}) { (finished) in
self.removeFromSuperview()
}
}
}else{
view.top = self.height
self.removeFromSuperview()
}
}
public class func bs_closeView() {
for view in UIApplication.shared.keyWindow?.subviews ?? [] {
if let view = view as? BsPresentView {
view.bs_closeView()
break
}
}
}
}
extension UIView {
@objc public func bs_closeViewToPresentView() {
if let presentView = self.superview as? BsPresentView {
presentView.bs_closeView()
}
}
}
//
// BskWebViewPreloadManager.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/11/22.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
import WebKit
class BsWebViewPreloadManager : NSObject {
private var dicPreloadedViews : [String : AnyObject] = [:]
static let share = BsWebViewPreloadManager()
override init() {
super.init()
addPreloadingView()
}
// /**
// 根据URL 预初始化若干WebView
// */
// - (void)addPreloadingView:(NSArray *)urls {
// for (NSString *url in urls) {
// if (![self.urls containsObject:url]) {
// [self.urls addObject: url];
// ReuseWebView *webView = [self createWebView];
// [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
// [self.preloadedViews addEntriesFromDictionary:@{url:webView}];
// }
// }
// }
}
// MARK: - 私有方法 methods
extension BsWebViewPreloadManager {
private func getWebview(urlString : String) -> WKWebView {
if let wk = dicPreloadedViews[urlString] {
return wk as! WKWebView
}
return createWebView(urlString: urlString)
}
private func createWebView(urlString : String) -> WKWebView {
let jScript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
let userScript = WKUserScript(source: jScript, injectionTime: WKUserScriptInjectionTime.atDocumentEnd, forMainFrameOnly: true)
let userContentController = WKUserContentController()
userContentController.addUserScript(userScript)
let cfg = WKWebViewConfiguration()
cfg.allowsInlineMediaPlayback = true
cfg.mediaTypesRequiringUserActionForPlayback = []
cfg.userContentController = userContentController
let wv = WKWebView(frame: UIScreen.main.bounds,configuration: cfg)
if let nsURL = URL(string: urlString){
let request = URLRequest(url: nsURL)
wv.load(request)
return wv
}
return WKWebView()
}
#if DEBUG
func onlyCreateWebView() -> WKWebView {
let jScript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
let userScript = WKUserScript(source: jScript, injectionTime: WKUserScriptInjectionTime.atDocumentEnd, forMainFrameOnly: true)
let userContentController = WKUserContentController()
userContentController.addUserScript(userScript)
let cfg = WKWebViewConfiguration()
cfg.allowsInlineMediaPlayback = true
cfg.mediaTypesRequiringUserActionForPlayback = []
cfg.userContentController = userContentController
let wv = WKWebView(frame: UIScreen.main.bounds,configuration: cfg)
return wv
}
#endif
}
// MARK: - public methods
extension BsWebViewPreloadManager {
func addPreloadingView() {
let arrUrl = [
BsConstant.URL.medical_center_url]
for item in arrUrl {
let wv = createWebView(urlString: item)
dicPreloadedViews[item] = wv
}
}
func getWebView(urlString : String) -> WKWebView {
let urls = dicPreloadedViews.keys
if urls.contains(urlString) == false {
return getWebview(urlString: urlString)
} else {
let wk = createWebView(urlString: urlString)
dicPreloadedViews[urlString] = wk
return wk
}
}
}
//
// MCClipImageTool.swift
// MCAPI
//
// Created by MC on 2018/9/18.
// Copyright © 2018年 MC. All rights reserved.
//
import UIKit
extension UIImage {
/**
* 翻转图片
*/
func rotationImage(orientation:UIImage.Orientation) -> UIImage {
var rotate : Double = 0.0;
var rect = CGRect.init()
var translateX : CGFloat = 0.0;
var translateY : CGFloat = 0.0;
var scaleX : CGFloat = 1.0;
var scaleY : CGFloat = 1.0;
let imageWidth = self.size.width
let imageHeight = self.size.height
// 根据方向旋转
switch (orientation) {
case .left:
rotate = Double.pi / 2;
rect = CGRect.init(x: 0, y: 0, width: imageHeight, height: imageWidth)
translateX = 0
translateY = -rect.size.width;
scaleY = rect.size.width/rect.size.height;
scaleX = rect.size.height/rect.size.width;
break;
case .right:
rotate = 33 * Double.pi / 2;
rect = CGRect.init(x: 0, y: 0, width: imageHeight, height: imageWidth)
translateX = -rect.size.height;
translateY = 0;
scaleY = rect.size.width/rect.size.height;
scaleX = rect.size.height/rect.size.width;
break;
case .down:
rotate = Double.pi
rect = CGRect.init(x: 0, y: 0, width: imageWidth, height: imageHeight)
translateX = -rect.size.width;
translateY = -rect.size.height;
break;
default:
rotate = 0.0;
rect = CGRect.init(x: 0, y: 0, width: imageWidth, height: imageHeight)
translateX = 0;
translateY = 0;
break;
}
//做CTM变换,并绘制图片
UIGraphicsBeginImageContext(rect.size);
let context = UIGraphicsGetCurrentContext();
context?.translateBy(x: 0, y: rect.size.height)
context?.scaleBy(x: 1, y: -1)
context?.rotate(by: CGFloat(rotate))
context?.translateBy(x: translateX, y: translateY)
context?.scaleBy(x: scaleX, y: scaleY)
context?.draw(self.cgImage!, in: CGRect.init(x: 0, y: 0, width: rect.size.width, height: rect.size.height))
let newPic = UIGraphicsGetImageFromCurrentImageContext();
return newPic!
}
/**
* 判断图片和裁剪框的关系类型
*/
func judgeRelationTypeWithCropSize(_ cropSize: CGSize) -> Int {
var relationType = 0
let crop_W = cropSize.width
let crop_H = cropSize.height
let image_W = self.size.width
let image_H = self.size.height
let imageRadio = image_W / image_H
let cropRadio = crop_W / crop_H
/** 裁切框宽高比 > 1
0. 裁切框宽高比 >= 图片宽高比 imageView宽固定,高适配
1. 裁切框宽高比 < 图片宽高比 imageView高固定,宽适配
*/
/** 裁切框宽高比 = 1
2. 裁切框宽高比 >= 图片宽高比 imageView宽固定,高适配
3. 裁切框宽高比 < 图片宽高比 imageView高固定,宽适配
*/
/** 裁切框宽高比 < 1
4. 裁切框宽高比 >= 图片宽高比 imageView宽固定,高适配
5. 裁切框宽高比 < 图片宽高比 imageView高固定,宽适配
*/
if cropRadio > 1 {
if cropRadio >= imageRadio {
relationType = 0
} else {
relationType = 1
}
} else if cropRadio == 1 {
if cropRadio >= imageRadio {
relationType = 2
} else {
relationType = 3
}
} else {
if cropRadio >= imageRadio {
relationType = 4
} else {
relationType = 5
}
}
return relationType
}
// 将图片裁剪为圆形
func clipCircularImage() -> UIImage {
let imageWidth = self.size.width
let imageHeight = self.size.height
let arcCenterX = imageWidth / 2
let arcCenterY = imageHeight / 2
let radius = arcCenterX > arcCenterY ? arcCenterY : arcCenterX
UIGraphicsBeginImageContext(self.size)
let context = UIGraphicsGetCurrentContext()
context!.beginPath()
context?.addArc(center: CGPoint.init(x: arcCenterX, y: arcCenterY), radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: false)
context?.clip()
self.draw(in: CGRect.init(x: 0, y: 0, width: imageWidth, height: imageHeight))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext();
return newImage!
}
}
//
// MCClipImageViewController.swift
// MCAPI
//
// Created by MC on 2018/9/14.
// Copyright © 2018年 MC. All rights reserved.
//
import UIKit
import QuartzCore
import Accelerate
/**
待拓展的功能
* 1. (完成)旋转图片。
* 2. 标出来裁剪框的尺寸和比例
* 3. 手动调整裁剪框的比例
* 4. 是否添加水印
* 5. 开放裁切框的颜色和UI。
* 6. (完成)圆形裁切框
* 7.
*/
protocol MCClipImageViewControllerDelegate : NSObjectProtocol {
func MCClipImageDidCancel()
func MCClipImageClipping(image:UIImage)
}
class MCClipImageViewController: UIViewController {
weak var delegate : MCClipImageViewControllerDelegate?
public var isRound = false
// 裁剪的目标图片
private var targetImage : UIImage = UIImage()
// 裁剪的区域
private var cropSize : CGSize = CGSize.init(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.width/2)
// 裁切框的frame
private var cropFrame = CGRect.init()
// 待裁切的图片和裁切框的宽高关系, 用于做裁切处理。
private var relationType = 0
/// 设置初始化UI的数据,记录待裁切的图片,要裁切的尺寸
func settingUIDataWithImage(_ image: UIImage,size:CGSize = CGSize.init(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.width/2)) {
targetImage = image
cropSize = size
//如果是圆形的话,对给的cropSize进行容错处理
if (self.isRound) {
if (cropSize.width >= cropSize.height) {
cropSize = CGSize.init(width: cropSize.height, height: cropSize.height)
}else{
cropSize = CGSize.init(width: cropSize.width, height: cropSize.width)
}
}
// 判断裁切框和图片的关系,用于做
relationType = targetImage.judgeRelationTypeWithCropSize(cropSize)
// 填充图片数据
fillData()
// 根据图片尺寸和裁切框的尺寸设置scrollView的最小缩放比例
setMinZoomScale()
if isRound {
// 设置裁切的圆形区域
transparentCutCircularArea()
} else {
// 矩形裁切框
transparentCutSquareArea()
}
scrollViewDidZoom(scrollView)
}
override func viewDidLoad() {
super.viewDidLoad()
baseSetting()
initUI()
}
// 隐藏状态栏
override var prefersStatusBarHidden: Bool {
return true
}
// MARK: - Setter & Getter
private lazy var scrollView: UIScrollView = {
let view = UIScrollView()
view.delegate = self
view.scrollsToTop = false
view.maximumZoomScale = 10
view.frame = CGRect.init(x: 0, y: 0, width: selfWidth, height: selfHeight)
view.isUserInteractionEnabled = true
view.showsVerticalScrollIndicator = false
view.showsHorizontalScrollIndicator = false
return view
}()
private lazy var imageView: UIImageView = {
let view = UIImageView()
view.isUserInteractionEnabled = true
view.contentMode = UIView.ContentMode.scaleAspectFill
return view
}()
lazy var overLayView: UIView = {
let view = UIView()
view.isUserInteractionEnabled = false
view.backgroundColor = .commonColor90
return view
}()
lazy var cancelButton: UIButton = {
let button = UIButton(type: .custom)
button.setTitle("取消", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.titleLabel?.font = UIFont.PFSCM(ofSize: 14)
button.addTarget(self, action: #selector(cancelButtonClicked), for: .touchUpInside)
return button
}()
lazy var rotatingButton: UIButton = {
let button = UIButton(type: .custom)
let path = Bundle.main.path(forResource: "ClipImage_rotating", ofType: "png", inDirectory: nil)
let image = UIImage.init(contentsOfFile: path!)
button.setImage(image, for: .normal)
button.addTarget(self, action: #selector(rotatingButtonClicked), for: .touchUpInside)
button.isHidden = true
return button
}()
lazy var sureButton: UIButton = {
let button = UIButton(type: .custom)
button.setTitle("确定", for: .normal)
button.setTitleColor(UIColor.mainColor, for: .normal)
button.titleLabel?.font = UIFont.PFSCM(ofSize: 14)
button.addTarget(self, action: #selector(sureButtonClicked), for: .touchUpInside)
return button
}()
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
cancelButton.snp.makeConstraints { make in
make.left.equalTo(16)
make.bottom.equalTo(UIDevice.bs_safeDistanceBottom()).offset(-50)
make.width.equalTo(80)
make.height.equalTo(40)
}
sureButton.snp.makeConstraints { make in
make.right.equalTo(-16)
make.bottom.equalTo(UIDevice.bs_safeDistanceBottom()).offset(-50)
make.width.equalTo(80)
make.height.equalTo(40)
}
}
}
// MARK: - 通知回调,闭包回调,点击事件
extension MCClipImageViewController {
// 取消
@objc func cancelButtonClicked() {
if delegate != nil {
delegate?.MCClipImageDidCancel()
}
self.dismiss(animated: true, completion: nil)
}
// 确定
@objc func sureButtonClicked() {
var image = getClippingImage()
if isRound {
image = image.clipCircularImage()
}
if delegate != nil {
delegate?.MCClipImageClipping(image: image)
}
self.dismiss(animated: true, completion: nil)
}
// 图片旋转
@objc func rotatingButtonClicked() {
// 清空之前设置的scale。否则会错乱
scrollView.minimumZoomScale = 1.0
scrollView.setZoomScale(1.0, animated: true)
targetImage = targetImage.rotationImage(orientation: .left)
imageView.image = targetImage
settingUIDataWithImage(targetImage, size: cropSize)
}
}
// MARK: - UI的处理,通知的接收
extension MCClipImageViewController {
func baseSetting() {
view.backgroundColor = UIColor.black
automaticallyAdjustsScrollViewInsets = false
}
func initUI() {
view.addSubview(scrollView)
scrollView.addSubview(imageView)
view.addSubview(overLayView)
view.addSubview(cancelButton)
view.addSubview(sureButton)
view.addSubview(rotatingButton)
let y = selfHeight - 25 - MCSafeAreaBottomHeight - 20
cancelButton.frame = CGRect.init(x: 20, y: y, width: 25, height: 25)
sureButton.frame = CGRect.init(x: selfWidth - 20 - 30, y: y, width: 25, height: 25)
rotatingButton.frame = CGRect.init(x: selfWidth/2 - 15, y: y, width: 25, height: 25)
overLayView.frame = view.frame
scrollView.frame = CGRect.init(x: 0, y: 0, width: selfWidth, height: selfHeight)
}
func fillData() {
// 1.添加图片
imageView.image = targetImage
// 2.设置裁剪区域
let x = (selfWidth - cropSize.width) / 2
let y = (selfHeight - cropSize.height) / 2
cropFrame = CGRect.init(x: x, y: y, width: cropSize.width, height: cropSize.height)
// 3.计算imgeView的frame
let imageW = targetImage.size.width
let imageH = targetImage.size.height
let cropW = cropSize.width
let cropH = cropSize.height
var imageViewW,imageViewH,imageViewX,imageViewY : CGFloat
switch relationType {
case 0,1: // 裁切框宽高比 > 0
imageViewW = cropW
imageViewH = imageH * imageViewW / imageW
imageViewX = (selfWidth - imageViewW) / 2
imageViewY = (selfHeight - imageViewH)/2
case 2,3: // 裁切框宽高比 = 0
imageViewW = cropW
imageViewH = imageH * imageViewW / imageW
imageViewX = (selfWidth - imageViewW) / 2
imageViewY = (selfHeight - imageViewH)/2
default:
imageViewH = cropH
imageViewW = imageW * imageViewH / imageH
imageViewX = (selfWidth - imageViewW) / 2
imageViewY = (selfHeight - imageViewH)/2
}
imageView.frame = CGRect.init(x: imageViewX, y: imageViewY, width: imageViewW, height: imageViewH)
}
//设置矩形裁剪区域
func transparentCutSquareArea() {
let alphaRect = CGRect.init(x: 0, y: 0, width: selfWidth, height: selfHeight)
let alphaPath = UIBezierPath.init(rect: alphaRect)
let squarePath = UIBezierPath.init(rect: cropFrame)
alphaPath.append(squarePath)
let shapeLayer = CAShapeLayer.init()
shapeLayer.path = alphaPath.cgPath
shapeLayer.fillRule = CAShapeLayerFillRule.evenOdd
overLayView.layer.mask = shapeLayer
//裁剪框
let cropRect_x = cropFrame.origin.x
let cropRect_y = cropFrame.origin.y
let cropRect_w = cropFrame.size.width
let cropRect_h = cropFrame.size.height
let cropRect = CGRect.init(x: cropRect_x, y: cropRect_y, width: cropRect_w, height: cropRect_h)
let cropPath = UIBezierPath.init(rect: cropRect)
let cropLayer = CAShapeLayer.init()
cropLayer.path = cropPath.cgPath
cropLayer.fillColor = UIColor.white.cgColor
cropLayer.strokeColor = UIColor.white.cgColor
overLayView.layer.addSublayer(cropLayer)
}
//设置圆形裁剪区域
func transparentCutCircularArea() {
let arcX = cropFrame.origin.x + cropFrame.size.width/2
let arcY = cropFrame.origin.y + cropFrame.size.height/2
var arcRadius : CGFloat = 0
if (cropSize.height > cropSize.width) {
arcRadius = cropSize.width/2
}else{
arcRadius = cropSize.height/2
}
//圆形透明区域
let alphaPath = UIBezierPath.init(rect: CGRect.init(x: 0, y: 0, width: selfWidth, height: selfHeight))
let arcPath = UIBezierPath.init(arcCenter: CGPoint.init(x: arcX, y: arcY), radius: arcRadius, startAngle: 0, endAngle: CGFloat(2*Double.pi), clockwise: false)
alphaPath.append(arcPath)
let layer = CAShapeLayer.init()
layer.path = alphaPath.cgPath
layer.fillRule = CAShapeLayerFillRule.evenOdd
overLayView.layer.mask = layer
//裁剪框
let cropPath = UIBezierPath.init(arcCenter: CGPoint.init(x: arcX, y: arcY), radius: arcRadius+1, startAngle: 0, endAngle: CGFloat(2*Double.pi), clockwise: false)
let cropLayer = CAShapeLayer.init()
cropLayer.path = cropPath.cgPath
cropLayer.strokeColor = UIColor.commonColor90.cgColor
cropLayer.fillColor = UIColor.commonColor90.cgColor
overLayView.layer.addSublayer(cropLayer)
}
// 设置最小缩放比例
func setMinZoomScale() {
// 设置最小的缩放比例。 自动填满裁剪区域
var scale : CGFloat = 0
let imageViewW = imageView.frame.size.width
let imageViewH = imageView.frame.size.height
let cropW = cropSize.width
let cropH = cropSize.height
switch relationType {
case 0: // 裁切框宽高比 > 1,并且裁切框宽高比 >= 图片宽高比
scale = imageViewW / cropW
case 1: // 裁切框宽高比 > 1,并且裁切框宽高比 < 图片宽高比
scale = cropH / imageViewH
case 2: // 裁切框宽高比 = 1, 并且裁切框宽高比 >= 图片宽高比
scale = cropW / imageViewW
case 3: // 裁切框宽高比 = 1, 并且裁切框宽高比 < 图片宽高比
scale = cropH / imageViewH
case 4: // 裁切框宽高比 < 1,并且裁切框宽高比 >= 图片宽高比
scale = cropW / imageViewW
default: // 裁切框宽高比 < 1,并且裁切框宽高比 < 图片框宽高比
scale = cropW / imageViewW
}
//自动缩放填满裁剪区域
self.scrollView.setZoomScale(scale, animated: false)
//设置刚好填充满裁剪区域的缩放比例,为最小缩放比例
self.scrollView.minimumZoomScale = scale
}
// 获取被裁剪的图片
func getClippingImage() -> UIImage {
/** 步骤
* 1. 获取图片和imageView的缩放比例。
* 2. 获取ImageView的缩放比例,即scrollView.zoomScale
* 3. 获取ImageView的原始坐标
* 4. 计算缩放后的坐标
* 5. 计算裁剪区域在原始图片上的位置
* 6. 裁剪图片,没有了release方法,CGImage会存在内存泄露么
*/
//图片大小和当前imageView的缩放比例
let scaleRatio = targetImage.size.width / imageView.frame.size.width
//scrollView的缩放比例,即是ImageView的缩放比例
let scrollScale = self.scrollView.zoomScale
//裁剪框的 左上、右上和左下三个点在初始ImageView上的坐标位置(注意:转换后的坐标为原始ImageView的坐标计算的,而非缩放后的)
var leftTopPoint = view.convert(cropFrame.origin, to: imageView)
var rightTopPoint = view.convert(CGPoint.init(x: cropFrame.origin.x + cropSize.width, y: cropFrame.origin.y), to: imageView)
var leftBottomPoint = view.convert(CGPoint.init(x: cropFrame.origin.x, y: cropFrame.origin.y + cropSize.height), to: imageView)
//计算三个点在缩放后imageView上的坐标
leftTopPoint = CGPoint.init(x: leftTopPoint.x * scrollScale, y: leftTopPoint.y*scrollScale)
rightTopPoint = CGPoint.init(x: rightTopPoint.x * scrollScale, y: rightTopPoint.y*scrollScale)
leftBottomPoint = CGPoint.init(x: leftBottomPoint.x * scrollScale, y: leftBottomPoint.y*scrollScale)
//计算裁剪区域在原始图片上的位置
let width = (rightTopPoint.x - leftTopPoint.x ) * scaleRatio
let height = (leftBottomPoint.y - leftTopPoint.y) * scaleRatio
let myImageRect = CGRect.init(x: leftTopPoint.x * scaleRatio, y: leftTopPoint.y*scaleRatio, width: width, height: height)
//裁剪图片
let imageRef : CGImage = targetImage.cgImage!
let subImageRef = imageRef.cropping(to: myImageRect)
UIGraphicsBeginImageContextWithOptions(myImageRect.size, true, 0)
let context = UIGraphicsGetCurrentContext()
context?.draw(subImageRef!, in: CGRect.init(x: 0, y: 0, width: myImageRect.size.width, height: myImageRect.size.height))
let subImage = UIImage.init(cgImage: subImageRef!)
UIGraphicsEndImageContext()
return subImage
}
}
// MARK: - UIScrollViewDelegate
extension MCClipImageViewController: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
//图片比例改变以后,让改变后的ImageView保持在ScrollView的中央
let size_W = scrollView.bounds.size.width
let size_H = scrollView.bounds.size.height
let contentSize_W = scrollView.contentSize.width
let contentSize_H = scrollView.contentSize.height
let offsetX = (size_W > contentSize_W) ? (size_W - contentSize_W) * 0.5 : 0.0
let offsetY = (size_H > contentSize_H) ? (size_H - contentSize_H) * 0.5 : 0.0
imageView.center = CGPoint.init(x: contentSize_W * 0.5 + offsetX, y: contentSize_H * 0.5 + offsetY)
//设置scrollView的contentSize,最小为self.view.frame.size
let scrollW = contentSize_W >= selfWidth ? contentSize_W : selfWidth
let scrollH = contentSize_H >= selfHeight ? contentSize_H : selfHeight
scrollView.contentSize = CGSize.init(width: scrollW, height: scrollH)
//设置scrollView的contentInset
let imageWidth = imageView.frame.size.width;
let imageHeight = imageView.frame.size.height;
let cropWidth = cropSize.width;
let cropHeight = cropSize.height;
var leftRightInset : CGFloat = 0;
var topBottomInset : CGFloat = 0;
//imageview的大小和裁剪框大小的三种情况,保证imageview最多能滑动到裁剪框的边缘
if (imageWidth <= cropWidth) {
leftRightInset = 0;
} else if (imageWidth >= cropWidth && imageWidth <= selfWidth) {
leftRightInset = (imageWidth - cropWidth) * 0.5
}else{
leftRightInset = (selfWidth - cropSize.width) * 0.5
}
if (imageHeight <= cropHeight) {
topBottomInset = 0
} else if (imageHeight >= cropHeight && imageHeight <= selfHeight) {
topBottomInset = (imageHeight - cropHeight) * 0.5
} else {
topBottomInset = (selfHeight - cropSize.height) * 0.5
}
scrollView.contentInset = UIEdgeInsets(top: topBottomInset, left: leftRightInset, bottom: topBottomInset, right: leftRightInset)
}
}
//-状态栏高度
fileprivate let MCStatusBarHeight : CGFloat = UIApplication.shared.statusBarFrame.size.height
//-导航栏高度
fileprivate func MCNavigationBarHeight(_ target:UIViewController) -> CGFloat {
if target.navigationController != nil {
return target.navigationController?.navigationBar.frame.size.height ?? 44
} else {
return 44
}
}
//-底部安全区域
fileprivate let MCSafeAreaBottomHeight : CGFloat = (UIScreen.main.bounds.size.height == 812 ? 34 : 0)
// 屏幕的宽高
fileprivate var selfWidth = UIScreen.main.bounds.size.width
fileprivate var selfHeight = UIScreen.main.bounds.size.height
//
// BsAllApiName.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/10/18.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
class YHAllApiName {
//===============登录相关 开始===============
//手机号码、密码 登录
static let loginByPwd = "user/UserLoginV2/loginByPwd"
//手机号码、验证码 登录
static let loginByVerifyCode = "user/UserLoginV2/loginByPhone"
//更改手机号
static let changedPhone = "user/User/changeUserPhone"
//获取验证码
static let verifyCode = "user/VerificationCodeV2/sendCode"
//手机一键登录
static let loginByOnePress = "user/UserLoginV2/oneClickPhoneLogin"
//苹果登录
static let appleLogin = "user/UserLoginV2/appleLogin"
//苹果登录绑定手机号
static let appleLoginBindPhone = "user/UserLoginV2/appleLoginBindPhone"
//手机号码是否注册
static let checkPhoneReigster = "user/UserLoginV2/checkPhoneRegister"
//修改密码
static let resetPwd = "user/user/editUserPassword"
//用户注销
static let unRegister = "user/user/destroy"
//检查更新
static let checkAppLastVersion = "system/AppVersion/getLastestVersion"
//===============登录相关 结束===============
//===============用户信息相关 开始===============
//用户详细信息
static let userDetailInfo = "user/user/userDetail"
//更新 用户详细信息
static let updateUserInfo = "user/user/updateUserInfo"
//上传图片
static let uploadImage = "system/Media/uploadBase64Image"
//确认用户是否有未读信息
static let reqUnreadMsgNum = "message/officialNotification/latestMonthUnreadNum"
//===============用户信息相关 结束===============
//===============用户反馈 开始===============
//用户反馈
static let userFeedBack = "user/Feedback/feedback"
//===============用户反馈 结束===============
//===============我的档案 开始===============
//用户额外信息
static let userExtInfo = "user/user/getUserExtInfo"
//获取用户信息选择项目
static let userSelection = "dictionary/DataDictionary/getUserDataList"
//获取用户提交过的健康状态详情
static let userHealthConditionDetail = "health/HealthCondition/getUserHealthConditionDetail"
//获取用户相关的健康状态问题列表
static let userHealthConditionList = "health/HealthCondition/getHealthConditionList"
//提交健康状态
static let submitHealthConditionReport = "health/HealthCondition/submitHealthConditionReport"
//===============我的档案 结束===============
//===============站内消息 开始===============
//获取站内信 通知列表
static let reqMsgList = "message/officialNotification/getNotificationList"
//更新信息为已读状态
static let updateMsgReadedStatus = "message/officialNotification/addReadRecord"
//查询用户是否有未读消息 --- for test hjl 还没有定义
static let queryMsgReadedStatus = "user/user/getUserExtInfo"
//===============站内消息 结束===============
//===============我的订单 开始===============
//获取 我的订单列表数据
static let reqMyOrderListData = "order/order/getOrderList"
//获取 订单详情
static let reqGetOrderDetail = "order/order/getOrderDetail"
//创建订单
static let reqCreateOrder = "order/order/createOrderV1"
//取消订单(只有待支付的才能取消)
static let reqCancelOrder = "order/order/cancelOrder"
//支付宝获取支付orderStr
static let reqAliPayOrderStr = "pay/pay/aliPayOrderStr"
//申请退款
static let reqApplyRefund = "order/order/applyRefund"
//===============我的订单 结束===============
//===============量表 开始===============
//获取已测评历史记录列表
static let reqCpHistoryList = "jiankang/JkRiskCheck/getUserCheckHistoryList"
//测评结果选项详情
static let cpResultOptionDetail = "jiankang/JkRiskCheck/getUserCheckHistoryQuestionSnapshot"
//获取量表测评分类
static let reqScaleCategory = "jiankang/JkRiskCheck/getCategory"
//获取量表测评列表
static let reqScaleCategoryList = "jiankang/JkRiskCheck/getSubject"
//量表详情(不含问题)
static let reqScaleDetail = "jiankang/JkRiskCheck/getRiskCheckSubjectDetail"
//量表详情&问题列表
static let reqScaleQuestions = "jiankang/JkRiskCheck/getRiskCheckQuestion"
//根据标签获取量表详情&问题列表(自我评估)
static let reqScaleQuestionsForSelf = "jiankang/JkRiskCheck/getRiskCheckQuestionByTag"
//提交测评数据
static let submitScaleInfo = "jiankang/JkRiskCheck/submit"
//获取已测评历史记录详情
static let reqGetUserCheckHistoryInfo = "jiankang/JkRiskCheck/getUserCheckHistoryInfo"
//获取是否已经做过自我评估(即是否填写过量表)
static let checkHasFillScale = "jiankang/JkRiskCheck/getSelfCheckStatus"
//===============量表 结束===============
//===============主页 开始===============
//获取banner列表
static let reqGetBanner = "banner/banner/getBanner"
//助眠专辑列表
static let reqGetSleepAlbumList = "album/album/getSleepAlbumList"
//===============主页 结束===============
//===============商品 开始===============
//获取上架的商品列表
static let reqGetProductList = "product/product/getProductList"
//获取指定商品详情
static let reqGetProduct = "product/product/getProduct"
//咨询服务展示选取的时间段
static let reqGetConsultTimePeriod = "product/consult/getConsultTimePeriod"
//===============商品 结束===============
//===============标签 开始===============
//全部的标签
static let allTagsApi = "user/tag/getRisk"
//已选标签
static let selectedTagsApi = "user/tag/getTags"
//设置标签
static let setTagsApi = "user/tag/set"
//===============标签 结束===============
//===============睡眠曲 开始===============
//睡眠曲分类列表
static let getSleepMusicCategorys = "sleep/SleepMusic/getAllCategorys"
//睡眠曲列表
static let getSleepMusicList = "sleep/SleepMusic/list"
//===============睡眠曲 结束===============
//===============科普视频 开始===============
//科普视频分类列表
static let getSleepVideoCategorys = "sleep/SleepVideo/getAllCategorys"
//科普视频列表
static let reqGetSleepVideoList = "sleep/SleepVideo/list"
//科普视频首页列表
static let reqGetIndexVideoList = "sleep/SleepVideo/getIndexVideoList"
//===============科普视频 结束===============
//===============公开讲座 开始===============
//公开讲座列表
static let getOpenLectureList = "lecture/OpenLecture/list"
//===============公开讲座 结束===============
//===============行业资讯 开始===============
//资讯首页列表数据
static let getIndexKnowledgeList = "jiankang/JkKnowledge/getIndexKnowledgeList"
//文章列表
static let reqGetKnowledgeList = "jiankang/JkKnowledge/getKnowledgeList"
//===============行业资讯 结束===============
//===============安睡 开始===============
//专辑详情
static let albumDetail = "album/album/getDetail"
//专辑中音乐列表
static let albumMusicList = "album/album/getAlbumMusicList"
//增加专辑浏览量
static let addAlbumLookCount = "album/album/addLookCount"
//视频或音乐点击播放埋点
static let addPlayCount = "sleep/SleepPlayHistory/play"
//===============安睡 开始===============
//===============门诊 开始===============
//获取门诊列表
static let getClinicList = "clinic/clinic/getClinicList"
//门诊详情
static let getClinicDetail = "clinic/clinic/getDetail"
//门诊绑定医生医生列表
static let getBindDoctorList = "clinic/clinic/getBindDoctorList"
//===============门诊 结束===============
//===============医生 开始===============
//医生列表
static let getDoctorList = "sleep/Doctor/getDoctorList"
//医生详情
static let getDoctorDetail = "sleep/Doctor/detail"
//医生直播及采访视频列表
static let getDoctorVideoList = "sleep/Doctor/getDoctorVideoList"
//首页搜索
static let searchClinicDoctorList = "clinic/clinic/searchClinicDoctorList"
//===============医生 结束===============
//===============用户-跳转链接 开始===============
//获取睡眠日记跳转链接
static let getSleepDiaryUrl = "user/user/getSleepDiaryUrl"
//获取cbti训练跳转链接
static let getCBTIJumpUrl = "user/user/getCBTIJumpUrl"
//===============用户-跳转链接 结束===============
}
//
// BsBaseurlManager.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/10/17.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
// MARK: - 管理项目开发中需要使用到的域名地址
class YHBaseUrlManager {
static let shared = YHBaseUrlManager()
//开发环境
static let baseUrlDev : String = "http://test.api.health.sleep321.com/"
//测试环境
//static let baseUrlTest : String = ""
//正式环境
static let baseUrlRelease : String = "https://api.health.sleep321.com/"
//h5开发环境
static let h5UrlDev : String = "https://cdn.sleep321.com/trial/"
//h5正式环境
static let h5UrlRelease : String = "https://cdn.sleep321.com/production/"
//用于外部编辑的
var urlForEditing : String?
// MARK: - 当前使用的url
func curURL() -> String {
#if DEBUG
return urlForEditing ?? YHBaseUrlManager.baseUrlDev
#else
return urlForEditing ?? YHBaseUrlManager.baseUrlRelease
#endif
}
// MARK: - 当前使用的url
func h5URL() -> String {
#if DEBUG
return YHBaseUrlManager.h5UrlDev
#else
return YHBaseUrlManager.h5UrlRelease
#endif
}
}
//
// BsNetworkStatusManager.swift
// BaiSiSMApp
//
// Created by davidhuang on 2022/11/8.
// Copyright © 2022 www.davidhuang.com. All rights reserved.
//
import UIKit
import Reachability
//网络状况 检测
class YHNetworkStatusManager {
static let shared = YHNetworkStatusManager()
var reachability: Reachability!
init(){
do {
reachability = try Reachability()
reachability.whenReachable = { reachability in
if reachability.connection == .wifi {
print("Reachable via WiFi2")
BsHUD.flash(message: "当前网络: wifi")
} else {
print("Reachable via Cellular")
BsHUD.flash(message: "当前网络: Cellular")
}
}
reachability.whenUnreachable = { _ in
print("Not reachable")
BsHUD.flash(message: "当前网络不可用")
}
} catch {
print("Unable to create Reachability")
return
}
}
var isNetWorkOK : Bool {
if reachability.connection != .unavailable {
return true
} else {
print("网络连接:不可用")
return false
}
}
var netType : Reachability.Connection {
return reachability.connection
}
func listenNetWorkStatus() {
NotificationCenter.default.addObserver(self, selector: #selector(reachabilityChanged(note:)), name: .reachabilityChanged, object: reachability)
do{
try reachability.startNotifier()
}catch{
print("could not start reachability notifier")
}
}
@objc func reachabilityChanged(note: Notification) {
switch reachability.connection {
case .wifi:
print("Reachable via WiFi1")
case .cellular:
print("Reachable via Cellular")
case .unavailable:
print("Network not reachable")
case .none:
print(".none")
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment