- 查找位置UI调整

- 更换头像 裁剪框
This commit is contained in:
linshujie 2026-09-16 17:47:52 +08:00
parent 9aa7b05066
commit 25aac678ac
12 changed files with 693 additions and 332 deletions

View File

@ -278,6 +278,7 @@
30EFF3E82FCA8C7000227D26 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30EFF3E92FCA8C7000227D26 /* AuthenticationServices.framework */; };
4392B1D9C125D873FCFCDDC9 /* app_catalog.json in Resources */ = {isa = PBXBuildFile; fileRef = 3C79865C3ED165AFDD091C29 /* app_catalog.json */; };
488FF21773B8E5021D8662B5 /* AppRestrictView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 787F87DD1CEBED1A4E1E9FC7 /* AppRestrictView.swift */; };
4A69FEABC5F1BA738C35A607 /* ImageCropVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C39ADD47D3C81F01F6289AC4 /* ImageCropVC.swift */; };
4AF6E5954D73E21F31F7A16B /* AppRestrictVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0112B7865829D5B6A4A0BD0 /* AppRestrictVC.swift */; };
557E8D5330187A5C0032AB51 /* BaseNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 557E8D5230187A5C0032AB51 /* BaseNavigationView.swift */; };
55B2179130217D6600784774 /* HomeView2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B2179030217D6600784774 /* HomeView2.swift */; };
@ -389,6 +390,7 @@
E866CAAE7D4E74EB7AD608B9 /* ITunesSearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE3D39F325FC7624C1D914D6 /* ITunesSearchService.swift */; };
EA13C1E21EC94958AC9755A8 /* FamilyActivityPickerHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF9D223AB35744F92E0E3E77 /* FamilyActivityPickerHost.swift */; };
F53E5BD11D671E5CED4E7408 /* ManagedSettingsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FE44DF1C46BDA7B98BC7859 /* ManagedSettingsUI.framework */; };
F9CE7D326B4F4F3C1FFAA447 /* ImageCropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E818049FA5A8557C434E37 /* ImageCropView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -713,6 +715,7 @@
475D33CAFA1E1911EB1F8D9F /* Pods_QuickLocation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_QuickLocation.framework; sourceTree = BUILT_PRODUCTS_DIR; };
47922567FCD34DBE9186F8FD /* AppCatalogStore.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AppCatalogStore.swift; path = AppRestrict/AppCatalogStore.swift; sourceTree = "<group>"; };
4CD6B965A124276661A84C4C /* FamilyControls.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FamilyControls.framework; path = System/Library/Frameworks/FamilyControls.framework; sourceTree = SDKROOT; };
51E818049FA5A8557C434E37 /* ImageCropView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ImageCropView.swift; sourceTree = "<group>"; };
557E8D5230187A5C0032AB51 /* BaseNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNavigationView.swift; sourceTree = "<group>"; };
55B2179030217D6600784774 /* HomeView2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView2.swift; sourceTree = "<group>"; };
55B2179230218CE100784774 /* GroupMemberView2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberView2.swift; sourceTree = "<group>"; };
@ -806,6 +809,7 @@
AC46E80B848E4012D192E4CE /* LockDistractView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LockDistractView.swift; sourceTree = "<group>"; };
BE3D39F325FC7624C1D914D6 /* ITunesSearchService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ITunesSearchService.swift; path = AppRestrict/ITunesSearchService.swift; sourceTree = "<group>"; };
BFFBDE92B94E7004F6B642AD /* AppRestrictShieldSettingsVC.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AppRestrictShieldSettingsVC.swift; path = AppRestrict/AppRestrictShieldSettingsVC.swift; sourceTree = "<group>"; };
C39ADD47D3C81F01F6289AC4 /* ImageCropVC.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ImageCropVC.swift; sourceTree = "<group>"; };
D0112B7865829D5B6A4A0BD0 /* AppRestrictVC.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AppRestrictVC.swift; path = AppRestrict/AppRestrictVC.swift; sourceTree = "<group>"; };
D64156B4A0E806901AB223FA /* DeviceActivityMonitorExtension.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DeviceActivityMonitorExtension.swift; path = DeviceActivityMonitorExtension/DeviceActivityMonitorExtension.swift; sourceTree = "<group>"; };
DA16D49AA46D4F6838340B55 /* Pods-QuickLocation.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-QuickLocation.debug.xcconfig"; path = "Target Support Files/Pods-QuickLocation/Pods-QuickLocation.debug.xcconfig"; sourceTree = "<group>"; };
@ -817,21 +821,29 @@
/* Begin PBXFileSystemSynchronizedRootGroup section */
30CCDF8E2FE3E63B00F5214A /* sound */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = sound;
sourceTree = "<group>";
};
30CCDF902FE3E63B00F5214A /* video */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = video;
sourceTree = "<group>";
};
30CCE01E2FE3E64700F5214A /* lotties */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = lotties;
sourceTree = "<group>";
};
30D87CEF2FDFF52100E958FD /* TTGTagCollectionView */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = TTGTagCollectionView;
sourceTree = "<group>";
};
@ -991,6 +1003,7 @@
305A74DB2FCA8C7000227D26 /* CornerRadiusView */,
305A74E22FCA8C7000227D26 /* ImagePicker */,
305A74E62FCA8C7000227D26 /* TextTableViewCell */,
B2DDB0083141D63A991C9FEE /* ImageCrop */,
);
path = Component;
sourceTree = "<group>";
@ -2110,6 +2123,16 @@
path = Pods;
sourceTree = "<group>";
};
B2DDB0083141D63A991C9FEE /* ImageCrop */ = {
isa = PBXGroup;
children = (
51E818049FA5A8557C434E37 /* ImageCropView.swift */,
C39ADD47D3C81F01F6289AC4 /* ImageCropVC.swift */,
);
name = ImageCrop;
path = ImageCrop;
sourceTree = "<group>";
};
B79BE06D49443FE82A811D5C /* LockDistract */ = {
isa = PBXGroup;
children = (
@ -2704,6 +2727,8 @@
E3D4129BDA33540BA4E1292E /* LockDistractVC.swift in Sources */,
E866CAAE7D4E74EB7AD608B9 /* ITunesSearchService.swift in Sources */,
DB67C2EB2521042829A3DBA5 /* PairGuideStepsView.swift in Sources */,
F9CE7D326B4F4F3C1FFAA447 /* ImageCropView.swift in Sources */,
4A69FEABC5F1BA738C35A607 /* ImageCropVC.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "area_badge@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "area_badge@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@ -0,0 +1,54 @@
//
// ImageCropVC.swift
// QuickLocation
//
import UIKit
final class ImageCropVC: BaseViewController {
override var isNavigationBarHidden: Bool { true }
override var preferredStatusBarStyle: UIStatusBarStyle { .lightContent }
var onConfirm: ((UIImage) -> Void)?
var onCancel: (() -> Void)?
private let image: UIImage
private var rootView: ImageCropView!
init(image: UIImage) {
self.image = image
super.init(nibName: nil, bundle: nil)
modalPresentationStyle = .fullScreen
modalPresentationCapturesStatusBarAppearance = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
rootView = ImageCropView(frame: UIScreen.main.bounds)
view = rootView
}
override func viewDidLoad() {
super.viewDidLoad()
rootView.setImage(image)
rootView.onCancel = { [weak self] in
self?.dismiss(animated: true) {
self?.onCancel?()
}
}
rootView.onConfirm = { [weak self] in
guard let self else { return }
guard let cropped = self.rootView.croppedImage() else {
DLToast.show(text: "图片处理失败")
return
}
self.dismiss(animated: true) {
self.onConfirm?(cropped)
}
}
}
}

View File

@ -0,0 +1,314 @@
//
// ImageCropView.swift
// QuickLocation
//
import UIKit
final class ImageCropView: UIView, UIScrollViewDelegate {
var onCancel: (() -> Void)?
var onConfirm: (() -> Void)?
private let scrollView = UIScrollView()
private let imageView = UIImageView()
private let overlayView = ImageCropOverlayView()
private let topBar = UIView()
private let bottomBar = UIView()
private var cropRect: CGRect = .zero
private var fittedCropRect: CGRect = .zero
private var pendingFit = false
private var userAdjusted = false
private var isFitting = false
private let bottomBarHeight: CGFloat = 56
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .black
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setImage(_ image: UIImage) {
imageView.image = image.fixOrientation()
pendingFit = true
setNeedsLayout()
}
func rotateClockwise() {
guard let image = imageView.image else { return }
setImage(image.rotatedClockwise())
}
func croppedImage() -> UIImage? {
guard let image = imageView.image, imageView.bounds.width > 0, cropRect.width > 0 else {
return nil
}
let zoom = scrollView.zoomScale
guard zoom > 0 else { return nil }
let origin = CGPoint(
x: (scrollView.contentOffset.x + cropRect.minX) / zoom,
y: (scrollView.contentOffset.y + cropRect.minY) / zoom
)
let size = CGSize(
width: cropRect.width / zoom,
height: cropRect.height / zoom
)
let rect = CGRect(origin: origin, size: size)
guard let cgImage = image.cgImage, imageView.bounds.width > 0, imageView.bounds.height > 0 else {
return nil
}
let scaleX = CGFloat(cgImage.width) / imageView.bounds.width
let scaleY = CGFloat(cgImage.height) / imageView.bounds.height
var pixelRect = CGRect(
x: rect.origin.x * scaleX,
y: rect.origin.y * scaleY,
width: rect.width * scaleX,
height: rect.height * scaleY
).integral
let bounds = CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height)
pixelRect = pixelRect.intersection(bounds)
guard pixelRect.width >= 1, pixelRect.height >= 1,
let cropped = cgImage.cropping(to: pixelRect) else {
return nil
}
return UIImage(cgImage: cropped, scale: 1, orientation: .up)
}
override func layoutSubviews() {
super.layoutSubviews()
scrollView.frame = bounds
overlayView.frame = bounds
let topSafe = safeAreaInsets.top
let bottomSafe = safeAreaInsets.bottom
topBar.frame = CGRect(x: 0, y: 0, width: bounds.width, height: topSafe + 44)
bottomBar.frame = CGRect(
x: 0,
y: bounds.height - bottomSafe - bottomBarHeight,
width: bounds.width,
height: bottomSafe + bottomBarHeight
)
let side = bounds.width
let availableTop = topBar.frame.maxY
let availableBottom = bottomBar.frame.minY
let originY = availableTop + max(0, (availableBottom - availableTop - side) / 2)
cropRect = CGRect(x: 0, y: originY, width: side, height: side)
overlayView.cropRect = cropRect
applyCropInsets()
if pendingFit, bounds.width > 0, imageView.image != nil {
pendingFit = false
userAdjusted = false
fitImageToCrop()
} else if !userAdjusted, cropRect != fittedCropRect, imageView.image != nil {
fitImageToCrop()
}
}
// MARK: - UIScrollViewDelegate
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
imageView
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if !isFitting { userAdjusted = true }
}
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
if !isFitting { userAdjusted = true }
}
// MARK: - Private
private func setupUI() {
scrollView.delegate = self
scrollView.showsHorizontalScrollIndicator = false
scrollView.showsVerticalScrollIndicator = false
scrollView.bounces = true
scrollView.alwaysBounceVertical = true
scrollView.alwaysBounceHorizontal = true
scrollView.bouncesZoom = true
scrollView.contentInsetAdjustmentBehavior = .never
scrollView.backgroundColor = .black
addSubview(scrollView)
imageView.contentMode = .scaleToFill
imageView.isUserInteractionEnabled = true
scrollView.addSubview(imageView)
overlayView.isUserInteractionEnabled = false
addSubview(overlayView)
addSubview(topBar)
addSubview(bottomBar)
let closeBtn = UIButton(type: .custom)
closeBtn.setImage(UIImage(named: "PhotoPicker/btn_close"), for: .normal)
closeBtn.addTarget(self, action: #selector(tapCancel), for: .touchUpInside)
topBar.addSubview(closeBtn)
closeBtn.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
closeBtn.leadingAnchor.constraint(equalTo: topBar.leadingAnchor, constant: 8),
closeBtn.bottomAnchor.constraint(equalTo: topBar.bottomAnchor),
closeBtn.widthAnchor.constraint(equalToConstant: 44),
closeBtn.heightAnchor.constraint(equalToConstant: 44)
])
let cancelBtn = UIButton(type: .custom)
cancelBtn.setTitle("取消", for: .normal)
cancelBtn.setTitleColor(.white, for: .normal)
cancelBtn.titleLabel?.font = .systemFont(ofSize: 16)
cancelBtn.addTarget(self, action: #selector(tapCancel), for: .touchUpInside)
bottomBar.addSubview(cancelBtn)
let rotateBtn = UIButton(type: .custom)
let rotateConfig = UIImage.SymbolConfiguration(pointSize: 20, weight: .regular)
rotateBtn.setImage(UIImage(systemName: "rotate.right", withConfiguration: rotateConfig), for: .normal)
rotateBtn.tintColor = .white
rotateBtn.addTarget(self, action: #selector(tapRotate), for: .touchUpInside)
bottomBar.addSubview(rotateBtn)
let confirmBtn = UIButton(type: .custom)
confirmBtn.setTitle("确定", for: .normal)
confirmBtn.setTitleColor(.white, for: .normal)
confirmBtn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
confirmBtn.backgroundColor = UIColor(hexStr: "#07C160")
confirmBtn.layer.cornerRadius = 8
confirmBtn.addTarget(self, action: #selector(tapConfirm), for: .touchUpInside)
bottomBar.addSubview(confirmBtn)
cancelBtn.translatesAutoresizingMaskIntoConstraints = false
rotateBtn.translatesAutoresizingMaskIntoConstraints = false
confirmBtn.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
cancelBtn.leadingAnchor.constraint(equalTo: bottomBar.leadingAnchor, constant: 20),
cancelBtn.topAnchor.constraint(equalTo: bottomBar.topAnchor, constant: 8),
cancelBtn.heightAnchor.constraint(equalToConstant: 40),
rotateBtn.centerXAnchor.constraint(equalTo: bottomBar.centerXAnchor),
rotateBtn.centerYAnchor.constraint(equalTo: cancelBtn.centerYAnchor),
rotateBtn.widthAnchor.constraint(equalToConstant: 44),
rotateBtn.heightAnchor.constraint(equalToConstant: 44),
confirmBtn.trailingAnchor.constraint(equalTo: bottomBar.trailingAnchor, constant: -16),
confirmBtn.centerYAnchor.constraint(equalTo: cancelBtn.centerYAnchor),
confirmBtn.widthAnchor.constraint(equalToConstant: 72),
confirmBtn.heightAnchor.constraint(equalToConstant: 36)
])
}
private func fitImageToCrop() {
guard let image = imageView.image, cropRect.width > 0 else { return }
let imageSize = image.size
guard imageSize.width > 0, imageSize.height > 0 else { return }
isFitting = true
scrollView.minimumZoomScale = 1
scrollView.maximumZoomScale = 1
scrollView.zoomScale = 1
imageView.transform = .identity
imageView.frame = CGRect(origin: .zero, size: imageSize)
scrollView.contentSize = imageSize
applyCropInsets()
let minScale = max(cropRect.width / imageSize.width, cropRect.height / imageSize.height)
scrollView.minimumZoomScale = minScale
scrollView.maximumZoomScale = max(minScale * 4, 4)
scrollView.zoomScale = minScale
let scaled = CGSize(width: imageSize.width * minScale, height: imageSize.height * minScale)
scrollView.contentOffset = CGPoint(
x: (scaled.width - cropRect.width) / 2 - scrollView.contentInset.left,
y: (scaled.height - cropRect.height) / 2 - scrollView.contentInset.top
)
fittedCropRect = cropRect
isFitting = false
}
private func applyCropInsets() {
scrollView.contentInset = UIEdgeInsets(
top: cropRect.minY,
left: cropRect.minX,
bottom: max(0, bounds.height - cropRect.maxY),
right: max(0, bounds.width - cropRect.maxX)
)
}
@objc private func tapCancel() {
onCancel?()
}
@objc private func tapRotate() {
rotateClockwise()
}
@objc private func tapConfirm() {
onConfirm?()
}
}
private final class ImageCropOverlayView: UIView {
var cropRect: CGRect = .zero {
didSet { updateMaskPath() }
}
private let dimLayer: CAShapeLayer = {
let layer = CAShapeLayer()
layer.fillRule = .evenOdd
layer.fillColor = UIColor.black.withAlphaComponent(0.55).cgColor
return layer
}()
override init(frame: CGRect) {
super.init(frame: frame)
isUserInteractionEnabled = false
backgroundColor = .clear
layer.addSublayer(dimLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
dimLayer.frame = bounds
updateMaskPath()
}
private func updateMaskPath() {
let path = UIBezierPath(rect: bounds)
if cropRect.width > 0, cropRect.height > 0 {
path.append(UIBezierPath(rect: cropRect))
}
dimLayer.path = path.cgPath
}
}
private extension UIImage {
func rotatedClockwise() -> UIImage {
let newSize = CGSize(width: size.height, height: size.width)
let format = UIGraphicsImageRendererFormat.default()
format.scale = scale
format.opaque = false
let renderer = UIGraphicsImageRenderer(size: newSize, format: format)
return renderer.image { ctx in
ctx.cgContext.translateBy(x: newSize.width, y: 0)
ctx.cgContext.rotate(by: .pi / 2)
draw(in: CGRect(origin: .zero, size: size))
}
}
}

View File

@ -5,48 +5,24 @@
import UIKit
/// hero + 使 + +
/// hero +
final class SearchLocationHeaderView: UIView {
let usageLab = UILabel()
let headlineLab = UILabel()
let subheadLab = UILabel()
let marqueeScrollView = UIScrollView()
/// + image backgroundColor
let heroImageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
setupMarquee()
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
func startMarqueeAnimation() {
marqueeScrollView.isHidden = true
return
marqueeScrollView.layer.removeAllAnimations()
marqueeScrollView.contentOffset.x = 0
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
guard let self = self else { return }
let contentW = self.marqueeScrollView.contentSize.width
guard contentW > self.marqueeScrollView.bounds.width else { return }
let duration = contentW / 100
UIView.animate(withDuration: duration, delay: 0, options: [.curveLinear, .repeat]) {
self.marqueeScrollView.contentOffset.x = contentW - self.marqueeScrollView.bounds.width
}
}
}
private func setupUI() {
addSubview(heroImageView)
addSubview(usageLab)
addSubview(headlineLab)
addSubview(subheadLab)
addSubview(marqueeScrollView)
heroImageView.backgroundColor = .clear
heroImageView.contentMode = .scaleAspectFit
@ -57,25 +33,12 @@ final class SearchLocationHeaderView: UIView {
.width(288)
.height(190)
let count = Int.random(in: 8000...15000)
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.groupingSeparator = ","
let countText = formatter.string(from: NSNumber(value: count)) ?? "\(count)"
usageLab.text = "\(countText)人正在使用此功能"
usageLab.font = .systemFont(ofSize: 14, weight: .medium)
usageLab.textColor = UIColor(hexStr: "#999999")
usageLab.textAlignment = .center
usageLab.layoutChain
.topToBottomOfView(heroImageView, offset: 42)
.centerX()
headlineLab.text = "实时查看TA的位置"
headlineLab.font = FontManager.boboBold(30)
headlineLab.textColor = UIColor(hexStr: "#293445")
headlineLab.textAlignment = .center
headlineLab.layoutChain
.topToBottomOfView(usageLab, offset: 37)
.topToBottomOfView(heroImageView, offset: 67)
.centerX()
subheadLab.text = "为你安心守护出行安全"
@ -83,82 +46,8 @@ final class SearchLocationHeaderView: UIView {
subheadLab.textColor = UIColor(hexStr: "#293445")
subheadLab.textAlignment = .center
subheadLab.layoutChain
.topToBottomOfView(headlineLab, offset: 18)
.topToBottomOfView(headlineLab, offset: 20)
.centerX()
.bottom()
marqueeScrollView.isHidden = true
marqueeScrollView.backgroundColor = .clear
marqueeScrollView.showsHorizontalScrollIndicator = false
marqueeScrollView.isScrollEnabled = false
}
private func randomPhoneNumber() -> String {
let prefixes = [
"134","135","136","137","138","139","147","150","151","152","157","158","159","178","182","183","184","187","188","198",
"130","131","132","145","155","156","166","175","176","185","186","196",
"133","149","153","173","177","180","181","189","191","199"
]
let prefix = prefixes.randomElement()!
var suffix = ""
for _ in 0..<8 { suffix.append("\(Int.random(in: 0...9))") }
return prefix + suffix
}
private func setupMarquee() {
let surnames = ["", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", ""]
let container = UIView()
container.backgroundColor = .clear
marqueeScrollView.addSubview(container)
var prevView: UIView?
for _ in 0..<10 {
let iconIndex = Int.random(in: 1...15)
let surname = surnames.randomElement() ?? ""
let maskedName = "\(surname)**"
let phone = randomPhoneNumber()
let maskedPhone = phone.prefix(3) + "******" + phone.suffix(2)
let itemView = UIView()
itemView.backgroundColor = UIColor(hexStr: "#EFF9FF")
itemView.cornerRadius = 8
container.addSubview(itemView)
let avatar = UIImageView(image: UIImage(named: "UserIcon/\(iconIndex)"))
avatar.contentMode = .scaleAspectFill
avatar.backgroundColor = UIColor(hexStr: "#E0E0E0")
avatar.cornerRadius = 12
avatar.clipsToBounds = true
itemView.addSubview(avatar)
let label = UILabel()
label.font = .systemFont(ofSize: 13, weight: .medium)
let fullText = "\(maskedName) 定位到了 \(maskedPhone)"
let attr = NSMutableAttributedString(string: fullText)
attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#16B3FF"),
range: NSRange(fullText.range(of: maskedName)!, in: fullText))
if let phoneRange = fullText.range(of: maskedPhone) {
attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#16B3FF"),
range: NSRange(phoneRange, in: fullText))
}
label.attributedText = attr
itemView.addSubview(label)
avatar.layoutChain.left(10).centerY().width(24).height(24)
label.layoutChain.leftToRightOfView(avatar, offset: 6).centerY().right(10)
itemView.layoutChain.centerY().height(30)
if let prev = prevView {
itemView.layoutChain.leftToRightOfView(prev, offset: 16)
} else {
itemView.layoutChain.left()
}
prevView = itemView
}
if let last = prevView {
container.layoutChain.edges().heightToView(marqueeScrollView).rightToView(last)
}
}
}

View File

@ -28,11 +28,13 @@ class SearchLocationResultVC: BaseViewController {
reactiveAction()
applyResultPresentation()
requestPhoneArea()
if code == 0 || code == 20005 {
requestGroupInfo()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
rootView.startMarqueeAnimation()
applyResultPresentation()
}
@ -45,9 +47,14 @@ class SearchLocationResultVC: BaseViewController {
rootView.actionBtn.rx.tap.subscribe(onNext: { _ in
switch self.code {
case 0:
var userInfo = self.memberData
let selectedKey = self.rootView.currentGroupKey
if !selectedKey.isEmpty {
userInfo["group_key"] = selectedKey
}
NotificationCenter.default.post(name: .ShowMemberLocationNotification,
object: nil,
userInfo: self.memberData)
userInfo: userInfo)
self.navigationController?.popToRootViewController(animated: true)
if let tab = self.tabBarController as? MainTabBarController
?? UIViewController.topMost?.tabBarController as? MainTabBarController {
@ -79,49 +86,90 @@ class SearchLocationResultVC: BaseViewController {
}
}
private func shareApp() {
let text = "快来极速定位,实时守护家人出行安全"
let activity = UIActivityViewController(activityItems: [text], applicationActivities: nil)
present(activity, animated: true)
}
private func handleCode() {
rootView.actionBtn.isHidden = true
rootView.inviteView.isHidden = true
switch code {
case 0:
rootView.configureResult(
status: "定位成功",
disclaimer: "点击去查看可获得具体位置",
showInviteRow: false
disclaimer: "选择圈子查看可获得具体位置",
showWarning: false,
showGroups: hasSharedGroups
)
rootView.actionBtn.setTitle("去查看", for: .normal)
rootView.actionBtn.isHidden = false
case 20004:
rootView.configureResult(
status: "用户未注册",
disclaimer: "根据最新的相关法规需用户双方均安装该APP才可实现定位未安装APP的用户将不再提供具体定位功能。",
showInviteRow: false
disclaimer: "您查询的用户未注册账号,无法提供具体定位,请邀请他加入您的圈子吧",
showWarning: true,
showGroups: false
)
rootView.actionBtn.setTitle("分享", for: .normal)
rootView.actionBtn.isHidden = false
case 20005:
rootView.configureResult(
status: "定位成功",
disclaimer: "根据最新的相关法规,您查询的用户与您不在同一个圈子时, 不再提供具体的位置定位邀请ta加入你的圈子后可随时查看位置。",
showInviteRow: false
disclaimer: "您查询的用户与您不在同一个圈子,无法提供具体定位,请邀请他加入您的圈子吧",
showWarning: true,
showGroups: false
)
rootView.actionBtn.setTitle("邀请TA加入", for: .normal)
rootView.actionBtn.isHidden = false
if groupModel == nil {
requestGroupInfo()
}
default:
break
}
}
private var hasSharedGroups: Bool {
guard let model = groupModel else { return false }
return !sharedGroups(from: model.groups).isEmpty
}
///
private func sharedGroups(from myGroups: [GroupInfoModel]) -> [GroupInfoModel] {
let keys = sharedGroupKeys()
guard !keys.isEmpty else { return [] }
let keySet = Set(keys)
return myGroups.filter { keySet.contains($0.group_key) }
}
private func sharedGroupKeys() -> [String] {
var keys: [String] = []
keys.append(contentsOf: groupKeys(in: memberData["common_groups"]))
keys.append(contentsOf: groupKeys(in: memberData["groups"]))
if let list = memberData["group_keys"] as? [Any] {
keys.append(contentsOf: list.compactMap(stringValue).filter { !$0.isEmpty })
}
let single = stringValue(memberData["group_key"])
if !single.isEmpty { keys.append(single) }
var seen = Set<String>()
return keys.filter { seen.insert($0).inserted }
}
private func groupKeys(in value: Any?) -> [String] {
guard let list = value as? [Any] else { return [] }
return list.compactMap { item in
if let dict = item as? [String: Any] {
return stringValue(dict["group_key"])
}
return stringValue(item)
}.filter { !$0.isEmpty }
}
private func stringValue(_ value: Any?) -> String {
switch value {
case let text as String:
return text.trimmingCharacters(in: .whitespacesAndNewlines)
case let number as NSNumber:
return number.stringValue
default:
return ""
}
}
private func requestPhoneArea() {
SystemService.phoneArea(phone: phone).subscribe(onNext: { response in
guard let data = response.data else { return }
@ -136,6 +184,15 @@ class SearchLocationResultVC: BaseViewController {
GroupService.groupInfo().subscribe { [weak self] response in
guard let self = self, let model = response.model else { return }
self.groupModel = model
if self.code == 0 {
let shared = self.sharedGroups(from: model.groups)
let preferred = self.stringValue(self.memberData["group_key"])
let selected = shared.contains(where: { $0.group_key == preferred })
? preferred
: (shared.first?.group_key ?? "")
self.rootView.reloadGroups(shared, selectedKey: selected)
}
self.handleCode()
if showPicker {
self.presentGroupPicker(with: model)
}

View File

@ -4,17 +4,28 @@
//
import UIKit
import Lottie
class SearchLocationResultView: UIView {
let headerView = SearchLocationHeaderView()
var onSelectGroup: ((String) -> Void)?
private var chipButtons: [UIButton] = []
private var selectedGroupKey: String = ""
private var chipHeightConstraint: NSLayoutConstraint?
private var chipTopConstraint: NSLayoutConstraint?
private func setupUI() {
addSubview(navBgView)
addSubview(navView)
addSubview(scrollView)
addSubview(actionBtn)
actionBtn.layoutChain
.edgesHorzontal(20)
.height(56)
.bottom(kSafeBottomMargin + 12)
navBgView.layoutChain
.edges(excludingEdge: .bottom)
.heightToWidth(160 / 375)
@ -28,22 +39,33 @@ class SearchLocationResultView: UIView {
.edges(excludingEdge: .top)
}
func startMarqueeAnimation() {
headerView.startMarqueeAnimation()
}
func configureResult(status: String, disclaimer: String, showInviteRow: Bool) {
func configureResult(status: String, disclaimer: String, showWarning: Bool, showGroups: Bool) {
statusPillLab.text = status
disclaimerLab.text = disclaimer
disclaimerLab.textAlignment = disclaimer.contains("去查看") ? .center : .left
disclaimerIcon.isHidden = disclaimer.contains("去查看")
inviteView.isHidden = !showInviteRow
// 72pt
inviteView.layoutChain
.topToBottomOfView(disclaimerLab, offset: showInviteRow ? 16 : 0)
.height(showInviteRow ? 72 : 0)
disclaimerLab.textAlignment = showWarning ? .left : .center
disclaimerIcon.isHidden = !showWarning
disclaimerRow.alignment = showWarning ? .top : .center
chipScrollView.isHidden = !showGroups
chipTopConstraint?.constant = showGroups ? 16 : 0
chipHeightConstraint?.constant = showGroups ? 32 : 0
}
func reloadGroups(_ groups: [GroupInfoModel], selectedKey: String) {
selectedGroupKey = selectedKey
chipButtons.forEach { $0.removeFromSuperview() }
chipButtons.removeAll()
chipStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
for group in groups {
let btn = makeChip(title: group.name, groupKey: group.group_key)
applyChipStyle(btn, selected: group.group_key == selectedKey)
chipStack.addArrangedSubview(btn)
chipButtons.append(btn)
}
}
var currentGroupKey: String { selectedGroupKey }
func updateSubViews() {
normalResultContentView.isHidden = false
}
@ -52,6 +74,7 @@ class SearchLocationResultView: UIView {
let iv = UIImageView()
iv.image = UIImage(named: "Common/navBar_bg_2")
iv.contentMode = .scaleAspectFill
iv.isHidden = true
return iv
}()
@ -75,35 +98,38 @@ class SearchLocationResultView: UIView {
contentView.addSubview(resultCard)
resultCard.layoutChain
.topToBottomOfView(headerView, offset: 20)
.edgesHorzontal(15)
.topToBottomOfView(headerView, offset: 34)
.edgesHorzontal(20)
contentView.addSubview(actionBtn)
actionBtn.layoutChain
.topToBottomOfView(resultCard, offset: 24)
.edgesHorzontal(30)
.height(56)
.bottom(kSafeBottomMargin + 30)
contentView.addSubview(areaBadgeView)
areaBadgeView.layoutChain
.topToView(resultCard, offset: -15)
.centerX(contentView, offset: 70)
.width(74)
.height(30)
.bottom()
return view
}()
lazy var resultCard: UIView = {
let card = UIView()
card.backgroundColor = UIColor(hexStr: "#EFF9FF")
card.cornerRadius = 16
card.backgroundColor = UIColor(hexStr: "#EFFAFF")
card.cornerRadius = 28
card.addSubview(resultStackView)
resultStackView.layoutChain.edges()
resultStackView.addArrangedSubview(normalResultContentView)
card.addSubview(normalResultContentView)
normalResultContentView.layoutChain
.top(22)
.edgesHorzontal()
.bottom()
return card
}()
private lazy var resultStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
return stackView
private lazy var areaBadgeView: UIImageView = {
let iv = UIImageView(image: UIImage(named: "SearchLocation/area_badge"))
iv.contentMode = .scaleAspectFit
return iv
}()
private lazy var normalResultContentView: UIView = {
@ -116,51 +142,41 @@ class SearchLocationResultView: UIView {
locationRow.addSubview(pinIcon)
locationRow.addSubview(phoneAreaLab)
// locationRow.addSubview(areaBlurView)
pinIcon.layoutChain.left().centerY().width(18).height(18)
phoneAreaLab.layoutChain
.leftToRightOfView(pinIcon, offset: 6)
.right()
.top()
.bottom()
// areaBlurView.layoutChain
// .topToView(phoneAreaLab, offset: -2)
// .bottomToView(phoneAreaLab, offset: 2)
// .leftToView(phoneAreaLab, offset: -4)
// .rightToView(phoneAreaLab, offset: 4)
locationRow.layoutChain.top(20).centerX()
locationRow.layoutChain.top().centerX()
let pillContainer = UIView()
pillContainer.backgroundColor = .white
pillContainer.cornerRadius = 14
pillContainer.cornerRadius = 16
contentView.addSubview(pillContainer)
pillContainer.addSubview(statusPillLab)
statusPillLab.layoutChain.edgesHorzontal(58).edgesVertical(13)
statusPillLab.layoutChain.edgesHorzontal(12).edgesVertical(14)
pillContainer.layoutChain
.topToBottomOfView(locationRow, offset: 11)
.centerX()
.topToBottomOfView(locationRow, offset: 14)
.edgesHorzontal(16)
.height(48)
contentView.addSubview(disclaimerIcon)
disclaimerIcon.layoutChain
.topToBottomOfView(pillContainer, offset: 14)
.left(24)
.width(12)
.height(12)
contentView.addSubview(disclaimerRow)
disclaimerRow.layoutChain
.topToBottomOfView(pillContainer, offset: 12)
.edgesHorzontal(20)
contentView.addSubview(disclaimerLab)
disclaimerLab.layoutChain
.topToBottomOfView(pillContainer, offset: 11)
.leftToRightOfView(disclaimerIcon, offset: 3)
.right(23)
contentView.addSubview(inviteView)
inviteView.layoutChain
.topToBottomOfView(disclaimerLab, offset: 0)
.edgesHorzontal(12)
.height(0)
.bottom(16)
contentView.addSubview(chipScrollView)
chipScrollView.translatesAutoresizingMaskIntoConstraints = false
chipTopConstraint = chipScrollView.topAnchor.constraint(equalTo: disclaimerRow.bottomAnchor, constant: 16)
chipHeightConstraint = chipScrollView.heightAnchor.constraint(equalToConstant: 32)
NSLayoutConstraint.activate([
chipTopConstraint!,
chipHeightConstraint!,
chipScrollView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
chipScrollView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
chipScrollView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20)
])
return contentView
}()
@ -172,15 +188,6 @@ class SearchLocationResultView: UIView {
return label
}()
lazy var areaBlurView: UIVisualEffectView = {
let view = UIVisualEffectView(effect: UIBlurEffect(style: .light))
view.isUserInteractionEnabled = false
view.isHidden = true
view.cornerRadius = 4
view.clipsToBounds = true
return view
}()
lazy var statusPillLab: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 16, weight: .medium)
@ -192,6 +199,7 @@ class SearchLocationResultView: UIView {
private lazy var disclaimerIcon: UIImageView = {
let iv = UIImageView(image: UIImage(named: "SearchLocation/warning"))
iv.contentMode = .scaleAspectFit
iv.layoutChain.width(12).height(12)
return iv
}()
@ -203,6 +211,14 @@ class SearchLocationResultView: UIView {
return label
}()
private lazy var disclaimerRow: UIStackView = {
let stack = UIStackView(arrangedSubviews: [disclaimerIcon, disclaimerLab])
stack.axis = .horizontal
stack.alignment = .top
stack.spacing = 4
return stack
}()
lazy var actionBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitleColor(.white, for: .normal)
@ -215,46 +231,64 @@ class SearchLocationResultView: UIView {
var inviteBtn: UIButton { actionBtn }
lazy var inviteView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.cornerRadius = 12
view.isHidden = true
view.addSubview(successBtn)
successBtn.layoutChain.right(12).centerY().width(90).height(36)
view.addSubview(successTipsLab)
successTipsLab.layoutChain.topToCenterYOfView(view).left(12)
view.addSubview(phoneLab)
phoneLab.layoutChain.bottomToCenterYOfView(view).left(12)
private lazy var chipScrollView: UIScrollView = {
let view = UIScrollView()
view.showsHorizontalScrollIndicator = false
view.alwaysBounceHorizontal = true
view.addSubview(chipStack)
chipStack.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
chipStack.topAnchor.constraint(equalTo: view.contentLayoutGuide.topAnchor),
chipStack.leadingAnchor.constraint(equalTo: view.contentLayoutGuide.leadingAnchor),
chipStack.bottomAnchor.constraint(equalTo: view.contentLayoutGuide.bottomAnchor),
chipStack.trailingAnchor.constraint(equalTo: view.contentLayoutGuide.trailingAnchor),
chipStack.heightAnchor.constraint(equalTo: view.frameLayoutGuide.heightAnchor)
])
return view
}()
lazy var phoneLab: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 16, weight: .bold)
label.textColor = UIColor(hexStr: "#333333")
return label
private lazy var chipStack: UIStackView = {
let stack = UIStackView()
stack.axis = .horizontal
stack.spacing = 8
stack.alignment = .fill
return stack
}()
lazy var successTipsLab: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 10, weight: .medium)
label.textColor = UIColor(hexStr: "#999999")
return label
}()
lazy var successBtn: UIButton = {
private func makeChip(title: String, groupKey: String) -> UIButton {
let btn = UIButton(type: .custom)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
btn.backgroundColor = UIColor(hexStr: "#16B3FF")
btn.cornerRadius = 18
btn.setTitle(title, for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 13, weight: .medium)
btn.contentEdgeInsets = UIEdgeInsets(top: 6, left: 12, bottom: 6, right: 12)
btn.layer.cornerRadius = 8
btn.layer.borderWidth = 1
btn.accessibilityIdentifier = groupKey
btn.addTarget(self, action: #selector(tapChip(_:)), for: .touchUpInside)
btn.setContentHuggingPriority(.required, for: .horizontal)
btn.setContentCompressionResistancePriority(.required, for: .horizontal)
return btn
}()
}
private func applyChipStyle(_ btn: UIButton, selected: Bool) {
if selected {
btn.setTitleColor(UIColor(hexStr: "#16B3FF"), for: .normal)
btn.backgroundColor = .white
btn.layer.borderColor = UIColor(hexStr: "#16B3FF").cgColor
} else {
btn.setTitleColor(UIColor(hexStr: "#C0C4CC"), for: .normal)
btn.backgroundColor = UIColor(hexStr: "#F2F3F5")
btn.layer.borderColor = UIColor.clear.cgColor
}
}
@objc private func tapChip(_ sender: UIButton) {
let key = sender.accessibilityIdentifier ?? ""
selectedGroupKey = key
for btn in chipButtons {
applyChipStyle(btn, selected: btn === sender)
}
onSelectGroup?(key)
}
override init(frame: CGRect) {
super.init(frame: .zero)

View File

@ -37,36 +37,32 @@ class SearchLocationVC: BaseViewController {
}
self.rootView.phoneInputTF.resignFirstResponder()
self.requestSearchPhone(phone: phone)
self.rootView.searchProgressView.isHidden = false
self.rootView.playVideo {
self.rootView.videoView.isHidden = true
self.rootView.searchProgressView.isHidden = true
guard self.code != -999 else {
DLToast.show(text: "发生未知错误,请重试")
return
}
if self.code == 0 {
AppRouter.push(Route.searchLocationResult, userInfo: ["phone": phone,
"code": self.code,
"memberData": self.memberData])
}
else {
AppRouter.push(Route.searchLocationResult, userInfo: ["phone": phone, "code": self.code])
}
}
// self.rootView.searchProgressView.isHidden = false
// self.rootView.playVideo {
// self.rootView.videoView.isHidden = true
// self.rootView.searchProgressView.isHidden = true
//
// guard self.code != -999 else {
// DLToast.show(text: "")
// return
// }
//
// if self.code == 0 {
// AppRouter.push(Route.searchLocationResult, userInfo: ["phone": phone,
// "code": self.code,
// "memberData": self.memberData])
// }
// else {
// AppRouter.push(Route.searchLocationResult, userInfo: ["phone": phone, "code": self.code])
// }
// }
}).disposed(by: disposeBag)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
rootView.startMarqueeAnimation()
}
// MARK: -
private func requestSearchPhone(phone: String) {
DLToast.showLoading()
SystemService.search(op_type: "phone",
number: phone).subscribe(onNext: { response in
self.code = 0
@ -81,9 +77,14 @@ class SearchLocationVC: BaseViewController {
if let data = response.data {
self.memberData = data
}
AppRouter.push(Route.searchLocationResult, userInfo: ["phone": phone,
"code": self.code,
"memberData": self.memberData])
}, onError: { (error) in
guard let code = error.underlyingError?.code else { return }
self.code = code
AppRouter.push(Route.searchLocationResult, userInfo: ["phone": phone, "code": self.code])
}).disposed(by: disposeBag)
}
}

View File

@ -8,8 +8,6 @@ import RxSwift
import RxCocoa
import AVFoundation
import Lottie
import Contacts
import ContactsUI
import URLNavigator
class SearchLocationView: UIView {
@ -66,17 +64,6 @@ class SearchLocationView: UIView {
.edges(excludingEdge: .top)
}
func startMarqueeAnimation() {
headerView.startMarqueeAnimation()
}
override func willMove(toWindow newWindow: UIWindow?) {
super.willMove(toWindow: newWindow)
if newWindow != nil {
startMarqueeAnimation()
}
}
lazy var navBgView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "Common/navBar_bg_2")
@ -108,8 +95,8 @@ class SearchLocationView: UIView {
contentView.addSubview(searchInputView)
searchInputView.layoutChain
.topToBottomOfView(headerView, offset: 45)
.edgesHorzontal(15)
.topToBottomOfView(headerView, offset: 70)
.edgesHorzontal(20)
.bottom(kSafeBottomMargin + 30, relation: .greaterThanOrEqual)
return view
@ -169,80 +156,66 @@ class SearchLocationView: UIView {
lazy var searchInputView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(hexStr: "#EFFAFF")
view.cornerRadius = 40
view.cornerRadius = 28
let inputView = UIView()
inputView.backgroundColor = .white
inputView.cornerRadius = 14
inputView.cornerRadius = 16
view.addSubview(inputView)
inputView.layoutChain
.top(20)
.edgesHorzontal(15)
.height(44)
.edgesHorzontal(16)
.height(52)
let contactsBtn = UIButton()
contactsBtn.setTitle("通讯录导入", for: .normal)
contactsBtn.setTitleColor(UIColor(hexStr: "#16B3FF"), for: .normal)
contactsBtn.titleLabel?.font = .systemFont(ofSize: 12, weight: .regular)
contactsBtn.rx.tap.subscribe(onNext: { [weak self] in
guard let self = self else { return }
self.phoneInputTF.resignFirstResponder()
let status = CNContactStore.authorizationStatus(for: .contacts)
switch status {
case .notDetermined:
CNContactStore().requestAccess(for: .contacts) { granted, _ in
DispatchQueue.main.async { if granted { self.presentContactPicker() } }
}
case .authorized:
self.presentContactPicker()
case .denied, .restricted:
DLToast.show(text: "请在设置中开启通讯录权限")
default:
break
}
}).disposed(by: disposeBag)
inputView.addSubview(contactsBtn)
contactsBtn.layoutChain.edgesVertical().width(60).right(12)
let prefixLab = UILabel()
prefixLab.text = "+86"
prefixLab.font = .systemFont(ofSize: 15, weight: .medium)
prefixLab.textColor = UIColor(hexStr: "#293445")
inputView.addSubview(prefixLab)
prefixLab.layoutChain
.left(16)
.centerY()
.width(40)
inputView.addSubview(phoneInputTF)
phoneInputTF.layoutChain
.edgesVertical(10)
.left(15)
.rightToLeftOfView(contactsBtn, offset: -8)
.leftToRightOfView(prefixLab, offset: 0)
.right(16)
let warningIcon = UIImageView(image: UIImage(named: "SearchLocation/warning"))
warningIcon.contentMode = .scaleAspectFit
view.addSubview(warningIcon)
warningIcon.layoutChain
.topToBottomOfView(inputView, offset: 10)
.left(15)
.topToBottomOfView(inputView, offset: 12)
.left(20)
.width(14)
.height(14)
let tipsLab = UILabel()
tipsLab.text = "我们不会存储和泄露你导入的通讯录隐私"
tipsLab.text = "仅支持查看有共同圈子成员的位置"
tipsLab.font = .systemFont(ofSize: 12, weight: .regular)
tipsLab.textColor = UIColor(hexStr: "#999999")
view.addSubview(tipsLab)
tipsLab.layoutChain
.leftToRightOfView(warningIcon, offset: 4)
.centerY(warningIcon)
.right(15)
.right(16)
view.addSubview(searchBtn)
searchBtn.layoutChain
.topToBottomOfView(warningIcon, offset: 24)
.edgesHorzontal(20)
.height(50)
.bottom(24)
.topToBottomOfView(warningIcon, offset: 16)
.edgesHorzontal(16)
.height(56)
.bottom(20)
return view
}()
lazy var phoneInputTF: UITextField = {
let textField = UITextField()
textField.font = .systemFont(ofSize: 14, weight: .medium)
textField.placeholder = "输入要查看的号码"
textField.font = .systemFont(ofSize: 15, weight: .medium)
textField.placeholder = "输入要查看的手机号码或ID"
textField.keyboardType = .numberPad
textField.returnKeyType = .done
return textField
@ -390,20 +363,4 @@ class SearchLocationView: UIView {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func presentContactPicker() {
let picker = CNContactPickerViewController()
picker.delegate = self
picker.displayedPropertyKeys = [CNContactPhoneNumbersKey]
UIViewController.topMost?.present(picker, animated: true)
}
}
extension SearchLocationView: CNContactPickerDelegate {
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
if let phone = contact.phoneNumbers.first?.value.stringValue {
phoneInputTF.text = phone.filter { $0.isNumber }
phoneInputTF.sendActions(for: .editingChanged)
}
}
}

View File

@ -57,7 +57,7 @@ class AvatarIconListVC: BaseViewController {
let picker = PhotoPickerVC(maxCount: 1)
picker.onConfirm = { [weak self] images in
guard let image = images.first else { return }
self?.uploadAndChangeAvatar(image)
self?.presentCrop(for: image)
}
present(picker, animated: true)
}
@ -70,13 +70,21 @@ class AvatarIconListVC: BaseViewController {
let vc = CameraCaptureVC(mode: .photo)
vc.onPhoto = { [weak self] image in
self?.dismiss(animated: true) {
self?.uploadAndChangeAvatar(image)
self?.presentCrop(for: image)
}
}
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true)
}
private func presentCrop(for image: UIImage) {
let crop = ImageCropVC(image: image)
crop.onConfirm = { [weak self] cropped in
self?.uploadAndChangeAvatar(cropped)
}
present(crop, animated: true)
}
private func uploadAndChangeAvatar(_ image: UIImage) {
guard let jpeg = image.jpegData(compressionQuality: 0.8) else {
DLToast.show(text: "图片处理失败")