UICollectionView

最近做需求做到了和 UICollectionView 相关的内容,特此补充整理知识漏洞

UICollectionView 包含多个子类 (subclass) 和协议 (protocol)

  1. 最高级别的控制和管理
    1. UICollectionView → 和 UITableView 一样,作为 UIScrollView 的继承子类,是主要展示内容的页面 作为一个容器,一个中间者,连接起五个不同的部分,并通知和引导数据的流通。
    2. UICollectionViewController
  2. 内容管理
  3. UIViewCollectionDataSource → 供给对应数据来源,告诉 UICollectionView 有几个 section,以及 section 当中应该展示的个数
    1. UIViewCollectionDelegate
      1. 处理事件顺序
        1. 手指按下
          1. shouldHighlightItemAtIndexPath → Yes 则向下执行,No 则到此为止
          2. didHighlightItemAtIndexPath → 高亮
        2. 手指松开
          1. didUnHighlightItemAtIndexPath → 取消高亮
          2. shouldSelectHighlightItemAtIndexPath → Yes 则向下执行,No 则到此为止
          3. didSelectItemAtIndexPath → 执行选择事件
  4. 展示
    1. UICollectionViewCell
      1. 和 UITableViewCell 类似,这些 Cells 作为 CollectionViewCell 的子类,组成了对应的页面内容 
      2. 通过 registerClass:forCellReuseIdentifier: 或 registerNib:forCellReuseIdentifier: 来实现添加自定义的子 Cell
      3. 可以通过
    2. UICollectionReusableView
      1. 当有其他的信息需要在 CollectionView 中展示,但他们不应该在 Cell 里面时,可以利用 supplementary view,一般包含 header 和 footer。
      2. 通过 registerClass:forSupplementaryViewOfKind:withReuseIdentifier: 或 registerNib:forSupplementaryViewOfKind:withReuseIdentifier: 来添加对应的自定义子Cell
  5. 布局
    1. UICollectionViewLayout → 告诉 UICollectionView 中每个 section 的大小和位置信息,可以改变对应视图的大小,位置以及其他的属性
      1. 常用属性:
        1. minimumLineSpacing 行间距,但在每行内 cell 大小都不一致的情况下, 会计算上行最底和下行最顶的间距
        2. minimumInterItemSpacing 列间距
        3. UICollectionViewScrollDirection 滑动方向
    2. UICollectionViewLayoutAttributes → 定义了 UICollectionView 的一些其他属性,例如边框,中心点,大小,是否可隐藏等等属性
    3. UICollectionViewUpdateItem
  6. 流动布局
    1. UICollectionFlowLayout → 用于实现如 grids 或者其他 line-based 的页面布局。 
    2. UICollectionDelegateFlowLayout

众多子类和协议中, UICollectionViewDataSource (datasource 实现),UICollectionViewDelegate 和 UICollectionViewDelegateFlowLayout(controller 实现)。

GCD 多线程

DispatchQueue(简称 GCD) 是 iOS 提供的一套允许代码以多核并发的方式执行程序的一种执行方式。

同步和异步的区别在于线程会等待同步任务执行完成,而线程不会等待异步任务执行完成,而同步和异步任务则是用 syn{ } 和 asyn { } 代码块涵盖的内容。

除此之外,GCD 还会通过两种队列(串行并发)来管理和决定其执行的顺序。串行的状态下,任务会按照队列的顺序一个一个地运行。而在并发的状态下,任务则会快速切换实现“伪同步”运行的效果。

以 GCD 为对象深入了解 iOS 多线程

  • 串行异步

会开启新的线程,按照顺序并一个一个完成任务

  • 串行同步

不会开启新的线程,同样是一步一步地完成任务,要求先执行对应的任务

  • 并行异步

会开启新的线程,并行操作多线程执行任务,任务交替执行

  • 并行同步

不会开启新的线程,所以等执行完一个任务才会执行下一个任务,会导致死锁

  • 主队列同步

在这种场景下,会导致死锁的产生,因为将新的任务添加到主队列中意为着主队列得先完成自己的任务才能完成新的任务,但由于同步,要求得先执行新的任务再执行主队列自己的任务,所以两个产生冲突,导致队列阻塞,导致死锁。

在取消网络请求的应用场景,可以直接在主队列添加任务,而不需要使用并行队列,因为倒计时的任务量不大,在每秒内都可以执行完毕,不会导致主线程拥塞。

GCD 提供几种多线程调用的方法:

  • dispatch_after

倒计时结束后,将block的任务添加到指定的任务队列去里面去

  • dispatch_once

用于执行单例模式

  • dispatch_block

用于实现一个 block 对象,可以方便后续的函数调用 block

  • dispatch_group

用于分组管理异步代码,包括以下三种用法:

  • notify (依赖)

可用于判断组内任务是否完成

  • wait (等待)

可用于等待任务完成

  • enter / group (手动管理计数)

手动添加/减少组内任务,必须一一对应

  • dispatch_semaphore

用于实现线程同步和加锁,同样是三种方法

  • create:用于控制并发数
  • wait:对应信号量的 P 操作,即请求资源,当 > 0 的时候会 – 1, < 0 的时候会等待
  • signal:对应信号量的 V 操作,即释放资源, + 1,可用于唤醒其他线程

可以用于监视某些事情的发生

参考

实操 https://leylfl.github.io/2018/01/16/%E6%B5%85%E8%B0%88iOS%E5%A4%9A%E7%BA%BF%E7%A8%8B-%E4%BD%BF%E7%94%A8%E7%AF%87/

样例:https://juejin.cn/post/6844903766701916168

NSLocalString

苹果针对本土化的服务一直精益求精,在代码中甚至专门用一个NSLocal的对象来实现本地化

NSLocal 的运行原理也非常简单,通过获取用户的系统语言 / 性别 / 国籍 / 地区等等,来实现同一个UI / 功能的不同呈现。如果不用本地化的方法写的话,可能需要用多个 if-else 来处理,但用 NSLocal 这个类似于字典的结构就可以很轻松地实现这个功能

NSLocalizedString

字符串的本土化,最普遍使用

有多种使用方法,一下为最基础的

NSLocalizedString(key, comment)
[NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:nil]

其中的 key 参数为表中的key – value 值的 key,comment 值则为注释

例如同样一个

NSLocalizedString("Debug", nil);

有三个本地化的String

//English
"Debug" = "debug";
//Simple-Chinese
"Debug" = "测试";
//Traditional-Chinese
"Debug" = "調試";

则在三种环境下的值也会不一样,判断的依旧是手机的配置

//TODO

图片及其他本地化 (参考:https://xiaovv.me/2017/06/04/Localization-of-iOS/

CALayer

UIView 作为 iOS 的 UI 最基础的界面显示,CALayer 是 UIView 的一个关键属性,在Apple Documentation给出的定义是

An object that manages image-based content and allows you to perform animations on that content.

翻译成中文就是管理基于图像内容的对象,且允许对此对象执行动画

总览

一般来说,layer是用来存储 Views 的后备数据,但 layer 本身也可以脱离 View 而独立使用。Layer 的主要作用是管理视觉内容,但 layer 本身就有一些可被设置的视觉属性,例如背景色 (background color),边框 (border) 和 阴影 (shadow)。除此之外,layer 还包括一些几何信息可在屏幕上显示的信息,例如位置 (position),大小 (size) 和变换 (transform)。

如果 layer 对象是由 View 创建的话,View 自己会自动将自己赋入 layer 的代理 (delegate),但对于自己创建的 layer,可以自己选择赋入的代理 View,可以动态绑定。

和 UIView 的关系

看完总览肯定有人觉得奇怪,既然 layer 管理了 UIView 的视觉内容,干嘛还要分开管理,为什么不把 layer 和 View 合二为一,用 layer 或者 View 来处理所有的事件呢?

  • 职责不同UIView 负责响应事件,Layer 负责渲染界面,单独把 layer 分出来就是为了处理仅渲染界面的情况UIView 和其他由 UIView 派生出来的类都继承自 UIResponder, 在 UIResponder 中定义了处理各种事件的和事件传递的接口,详情可看我之前关于 iOS 相应链的文章(链接),而 CALayer 则是直接继承自 NSObject,不会对事件进行处理。
  • 分工不同除此之外,UIView 主要针对显示内容的管理,而 CALayer 则侧重于显示内容的绘制也因此,在做 iOS 动画的时候,修改非 RootLayer 的属性(位置和背景色等)会默认产生隐式动画,而修改 UIView 则不会
  • 内部结构不同Layer 内部维护三部分 layer tree,分别是 presentLayer Tree(动画树),modeLayer Tree(模型树),Render Tree (渲染树)。

Mask

mask, 即遮罩,是一个可选的layer,他的 Alpha 通道值用于遮盖 layer 的内容

可以通过 mask.content 来处理,在 content 中设置相关参数,从而实现遮罩的效果

除此之外,还可以通过 CAShapeLayer 来画出遮罩的类型

引用

https://developer.apple.com/documentation/quartzcore/calayer
http://www.cocoachina.com/articles/13244
https://zsisme.gitbooks.io/ios-/content/index.html

CannyX

项目汇报

目标一:实时调用摄像头输出视频流到屏幕,提供按钮选择前置或后置

使用 AVCaptureSession( ) 来调用摄像头

let session = AVCaptureSession()
var previewLayer: AVCaptureVideoPreviewLayer!
var sequenceHandler = VNSequenceRequestHandler()
let dataOutputQueue = DispatchQueue(
  label: "video data queue",
  qos: .userInitiated,
  attributes: [],
  autoreleaseFrequency: .workItem)

处理摄像头的参数

    func configureCaptureSession() {
    // Define the capture device we want to use
    // default position : .back
    guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera,
                                               for: .video,
                                               position: .back) else {
      fatalError("No back video camera available")
    }

    // Connect the camera to the capture session input
    do {
      let cameraInput = try AVCaptureDeviceInput(device: camera)
      session.addInput(cameraInput)
    } catch {
      fatalError(error.localizedDescription)
    }

    // Create the video data output
    let videoOutput = AVCaptureVideoDataOutput()
    videoOutput.setSampleBufferDelegate(self, queue: dataOutputQueue)
    videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]

    // Add the video output to the capture session
    session.addOutput(videoOutput)

    let videoConnection = videoOutput.connection(with: .video)
    videoConnection?.videoOrientation = .portrait
    // Configure the preview layer
    previewLayer = AVCaptureVideoPreviewLayer(session: session)
    previewLayer.videoGravity = .resizeAspectFill
    previewLayer.frame = view.bounds
    view.layer.insertSublayer(previewLayer, at: 0)
  }

处理前置后置摄像头的替换

// Button Action function
@IBAction func cameraChangedButton(_ sender: Any) {
  // stop running the session to change camera state
  session.stopRunning() 

  // remove the current camera setting
    let currentCameraInput: AVCaptureInput = session.inputs[0]
  session.removeInput(currentCameraInput) 

  // setting new camera .back to .front and .front to .back
  newCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)

  //adding new camera setting
  let cameraInput = try AVCaptureDeviceInput(device: newCamera)
          session.addInput(cameraInput)
          session.outputs[0].connection(with: .video)?.videoOrientation = .portrait
  // Check if need to mirror the video
            session.outputs[0].connection(with: .video)?.isVideoMirrored = (cameraChangeButtonState.currentTitle == "Front" ? true : false)
    session.startRunning()
} 

目标二:提供按钮支持边缘检测功能

使用 Cocoapods 导入 OpenCV 库

pod 'OpenCV'

在 Swift 中无法直接调用 C++ 的库,需要用 OC 进行调用,并进行桥接实现混编。因此在本项目中,edgeDetector 作为 C++ 文件直接使用 OpenCV 库中的 Canny 函数检测边缘,并通过 OpenCVWrapper (OC 文件) 调用 edgeDetector 中的类, 最后在 Project-Bridging-Header.h 中桥接在 ViewController 中使用相关的 OC 函数

此处需将 OpenCVWrapper.m 改为 OpenCVWrapper.mm 以进行 OC 和 C++ 的混编

@implementation OpenCVWrapper : NSObject
- (UIImage *) detectEdgeIn: (UIImage *) image{
    // convert uiimage to mat
        cv::Mat opencvImage;
        UIImageToMat(image, opencvImage, true);
    // convert colorspace to the one expected by the lane detector algorithm (RGB)
        cv::Mat convertedColorSpaceImage;
        cv::cvtColor(opencvImage, convertedColorSpaceImage, COLOR_RGBA2RGB);

    // Run lane detection
    edgeDetector Detector;
    cv::Mat imageWithEdgeDetected = Detector.detect_edges(convertedColorSpaceImage);

    // convert mat to uiimage and return it to the caller
    return MatToUIImage(imageWithEdgeDetected);
}
@end

在 edgeDetector 中用获取边缘图

Mat edgeDetector::detect_edges(Mat image) {

    Mat greyScaledImage;
    cvtColor(image, greyScaledImage, COLOR_RGB2GRAY);

    Mat edgedOnlyImage;
    Canny(greyScaledImage, edgedOnlyImage, 50, 120);
    Mat newBImg(edgedOnlyImage.rows, edgedOnlyImage.cols, edgedOnlyImage.type());
    uchar* newBImgData = newBImg.data;
    uchar* binaryData = edgedOnlyImage.data;
    int step = edgedOnlyImage.step / sizeof(uchar);

    for (int i = 0; i<edgedOnlyImage.rows; i++)
        for (int j = 0; j<edgedOnlyImage.cols; j++)
            newBImgData[i*step + j] = 255 - binaryData[i*step + j];

    edgedOnlyImage = newBImg.clone();
    uchar r, g, b;
        // Converted canny image to BGRA4 channel image
    cvtColor(edgedOnlyImage, edgedOnlyImage, COLOR_GRAY2BGRA);
    for (int i = 0; i < edgedOnlyImage.rows; i++)
        for (int j = 0; j < edgedOnlyImage.cols; j++)
        {
            // R
            r = edgedOnlyImage.at<Vec4b>(i, j)[2];
            // G
            g = edgedOnlyImage.at<Vec4b>(i, j)[1];
            // B
            b = edgedOnlyImage.at<Vec4b>(i, j)[0];

            if (r > 220 && b > 220 && g > 220)
            {
                // A
                edgedOnlyImage.at<Vec4b>(i, j)[3] = 0;
            }
            if (r < 20 && b < 20 && g < 20)
            {
                // A
                edgedOnlyImage.at<Vec4b>(i, j)[1] = 255;
                edgedOnlyImage.at<Vec4b>(i, j)[2] = 0;
                edgedOnlyImage.at<Vec4b>(i, j)[0] = 0;
            }
        }
    return edgedOnlyImage;
}

通过 Project-Bridging-Header.h 中桥接,可以直接在 ViewController 中使用相关的 OC 函数

// get images from the camera
guard let  imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
        CVPixelBufferLockBaseAddress(imageBuffer, CVPixelBufferLockFlags.readOnly)
        let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)
        let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer)
        let width = CVPixelBufferGetWidth(imageBuffer)
        let height = CVPixelBufferGetHeight(imageBuffer)
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        var bitmapInfo: UInt32 = CGBitmapInfo.byteOrder32Little.rawValue
        bitmapInfo |= CGImageAlphaInfo.premultipliedFirst.rawValue & CGBitmapInfo.alphaInfoMask.rawValue
        let context = CGContext(data: baseAddress, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo)
        guard let quartzImage = context?.makeImage() else { return }
        CVPixelBufferUnlockBaseAddress(imageBuffer, CVPixelBufferLockFlags.readOnly)
        let image = UIImage(cgImage: quartzImage)

因为不能在此处使用UIKit相关的操作,所以需要放入 DispatchQueue.main.async 保证线程安全

let imageWithEdgeOverlay = OpenCVWrapper().detectEdge(in: image)
DispatchQueue.main.async {
  self.CannyView.image = imageWithEdgeOverlay
}

目标三:用 C++ 实现图像均值计算库,导入并实时计算图像均值

用 C++ 实现图像均值我还是沿用了 OpenCV 里的计算库, 考虑到最后导入到项目中还是需要混合编程, 再加上新版 Xcode 不支持写 C++ 的库和框架, 所以最后用 OC 实现

同样,在 OC 中使用混编需要将 .m 文件改成 .mm

@implementation AveragePiexlValueOC : NSObject 
- (double) PixelValueOC: (UIImage *) image{
    cv::Mat opencvImage;
    UIImageToMat(image, opencvImage, true);
    pixelValue calculator;
    double result = calculator.averagePixelValue(opencvImage);
    return result;
}
@end
double pixelValue::averagePixelValue(Mat image)
{
    Scalar mean;
    Scalar dev;
    meanStdDev ( image, mean, dev );
    return mean.val[0];
};

运行后获得 libAveragePiexlValueOC.a 的静态库,将其及 AveragePiexlValueOC.h 拉入项目中,在 Project-Bridging-Header.h 中桥接,便可以在 ViewController 中使用

和 CannyView 一样,需要放入 DispatchQueue.main.async

var averagePixelValueData2 = AveragePiexlValueOC().pixelValueOC(image)
DispatchQueue.main.async {
            self.dataShow.text = "AVERAGE PIXEL: "+String(format: "%.2f", averagePixelValueData2)
            self.CannyView.image = imageWithEdgeOverlay
        }

AppUI相关

App图标

App 启动页

App 主界面UI

对应层级图

最终效果展示

后置摄像头

前置摄像头

计算像素均值

获取边缘

Amazon Cognito

今早凌晨四点考完微观小测,突然想起来下周二要交任务了然而我现在的进度还十分缓慢QAQ。

面试官提到了用Amazon Cognito来对接后端, 亚马逊家的东西果然还是可以的,教程啥的都有,这里附上教程链接🔗

第一步: 创建用户池

进入控制台 点击进入管理用户池

控制台

进入后点击创建用户池

点击创建

输入池名称并按照默认方式创建

在默认方式中进行选择设置

创建成功

到这一步就创建好了用户池

第二步:添加应用程序

如图所示,在应用程序客户端上添加客户端

建立完之后会有一个应用程序客户端 ID,需要记住

再勾选 Cognito User Pool

再输入回调URL和注销URL,如果是网页应用则应该使用https:// IOS或者安装则是用myapps://

IOS学习日记二

把想要设置的图片拉到 Assets.xcassets里面

doge

File > New > File中新建一个SwiftUI View的文件,我这里将其命名为CircleImage.swift

用Image来展示该图

import SwiftUI

struct CircleImage: View {
    var body: some View {
        Image("dogHead") // 只需要名字
    }
}

struct CircleImage_Previews: PreviewProvider {
    static var previews: some View {
        CircleImage()
    }
}

.clipShape(Circle())使得切割图像为圆形

var body: some View {
        Image("Doggie")
            .clipShape(Circle())
    }

The Circle type is a shape that you can use as a mask, or as a view by giving the circle a stroke or fill.

an overlay

使用overlay来给外围的圆圈添加参数

Color-> 颜色

LineWidth-> 线宽

.shadow() -> 增加阴影

struct CircleImage: View {
    var body: some View {
        Image("Doggie")
            .clipShape(Circle())
            .overlay(
                Circle().stroke(Color.gray, lineWidth: 4))
            .shadow(radius: 10)
    }
}

新建一个文件并引入MapKit

IOS学习日记

前几天凭着自己毫无swift或者ios开发的经验去面客户端开发被狠狠批了一顿,打算今天开始学一下IOS开发。

从SwiftUI开始学起,从Apple官网学的

VStack & HStack

VStack 是垂直排列部件的方式, 而HStack则是水平排列部件的方式。正如下面的“Turtle Rock” 和 “Joshua Tree National Park”,最终在手机布局上是垂直的。 而在HStack内的“Joshua Tree National Park” 和 “Hai” 则是水平分布的

VStack(alignment: .leading) { //Vertical Stack
            Text("Turtle Rock")
                .font(.title)
            HStack { //Horizon Stack
                Text("Joshua Tree National Park")
                    .font(.subheadline)
                Text("Hai")
                    .font(.subheadline)
            }
        }
视图
var body: some View {
        VStack(alignment: .leading) {
            Text("Turtle Rock")
                .font(.title)
            HStack {
                Text("Joshua Tree National Park")
                    .font(.subheadline)
                Spacer()
                Text("Hai")
                    .font(.subheadline)
            }
        }
        .padding()
    }

Spacer()

spacer expands to make its containing view use all of the space of its parent view, instead of having its size defined only by its contents.

就相当于用spacer来占满整个宽度

Padding()

用于修饰填充内边距