10. NSTableView Table 数据表格

表格是非常重要和复杂的一个控件,本节会用大量篇幅来把表格这东西力求讲清楚。

基本设置

表格结构

表格是 OS X 组件中为数不多采用了MVC设计模式来实现的控件,即tableView–dataSource–Delegate,这种分层架构给处理数据带来了极大的便利性。先了解下表格的组成,如下:
在这里插入图片描述

  • NSTableView:由NSTableHeaderView + NSTableRowView组成;
  • NSScrollView:NSScrollView包装了NSTableView,它和NSTableView构成了最外围的对象,NSTableRowView 的视图由NSScrollView来管理;
  • NSTableHeaderView:为表头,由一组 NSTableHeaderCell 组成;
  • NSTableRowView:表示内容区,由一组 NSTableCellView组成;

  • NSTableViewDataSource:由多个NSTableColumn(即列视图)来定义;NSTableRowView 的数据由NSTableViewDataSource来定义,NSTableViewDataSource定义了一系列的显示回调方法;
  • NSTableViewDelegate:NSTableRowView 的代理由NSTableViewDelegate来定义,它提供了数据加载时的回调方法;

以下是UI的层级结构:
在这里插入图片描述

table 属性

  • content mode:设置table的cell模式,推荐使用view-based,cell-based是一种老设计;
  • columns:表示表格有多少列;
  • header:是否显示表头;
  • horizontal grid和 vertical grid:是否显示表格线;
  • background color:单元格背景色;
  • selection:是否允许行多选;

column 属性

  • title:标题;
  • state:是否可以编辑列名称;
  • identifer:列的唯一标识符,用于在数据源和代理回调中使用;
  • Table Cell View 的identifer:用于数据量大时可从缓存中获取可复用的cell单元视图,用于性能优化;

设置表格外观

主要是颜色等外观样式

    @IBOutlet weak var tableView: NSTableView!
    
    func tableStyleConfig() {
        //表格网格线设置
        self.tableView.gridStyleMask = [NSTableView.GridLineStyle.dashedHorizontalGridLineMask,NSTableView.GridLineStyle.solidVerticalGridLineMask]
        //表格背景
        self.tableView.backgroundColor = NSColor.white
        //背景颜色交替
        self.tableView.usesAlternatingRowBackgroundColors = true
        //表格行选中样式
        self.tableView.selectionHighlightStyle = .regular
    }

表格数据绑定

以下是三种表格数据绑定方法,推荐第二种方式。

Cell-Based表格数据

  1. 首先修改 Table View 的Content Mode 为Cell Based;

  2. 选择column,修改identifier,与程序代码中数据定义相匹配(参考下边代码示例);
    在这里插入图片描述

  3. 可以往单元格中添加不同的 cell,注意是cell而不是控件,这样就可以实现表格中的个性化展示了,如下图:
    在这里插入图片描述

  4. 设置TableView的Deletegate和DataSource为默认的View Controller,拖动下图红框到UI导航的View Controller上面;
    在这里插入图片描述
    也可以通过代码设置 self.tableView.delegate = selfself.tableView.dataSource = self

  5. 定义表格数据,下面的updateData方法是自定义的,需要调用:

    //表格数据,这个datas属性会在协议实现时再加载使用
    var datas = [NSDictionary]()
    func updateData() {
        self.datas = [
            ["name":"john","address":"USA","gender":"male","married":(1)],
            ["name":"mary","address":"China","gender":"female","married":(0)],
            ["name":"park","address":"Japan","gender":"male","married":(0)],
            ["name":"Daba","address":"Russia","gender":"female","married":(1)],
        ]
    }
  1. 扩展协议,加载数据
extension ViewController: NSTableViewDataSource {
    
    //返回表格数据行数
    func numberOfRows(in tableView: NSTableView) -> Int {
        return self.datas.count
    }

    //正常只读显示
    func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
        let data = self.datas[row]
        //表格列的标识
        let key = tableColumn?.identifier
        //单元格数据
        let value = data[key!]
        return value
    } 
}

在这里插入图片描述

View-Based表格数据(推荐)

  1. 首先修改 Table View 的Content Mode 为View Based;
  2. 选择column,修改identifier,与数据相匹配;(同上)
  3. 可以往单元格中添加不同的控件,注意是控件而不是cell,这样就可以实现表格中的个性化展示了:
  4. 设置TableView的Deletegate和DataSource为默认的View Controller;(同上)
  5. 定义表格数据(同上)
  6. 扩展协议,加载数据,要实现两个协议
extension ViewController: NSTableViewDataSource {
    func numberOfRows(in tableView: NSTableView) -> Int {
        return self.datas.count
    }
}

extension ViewController: NSTableViewDelegate {
    
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        
        let data = self.datas[row]
        //表格列的标识
        let key = (tableColumn?.identifier)!
        //单元格数据
        let value = data[key]
        
        //根据表格列的标识,创建单元视图
        let view = tableView.makeView(withIdentifier: key, owner: self)
        let subviews = view?.subviews
        if (subviews?.count)!<=0 {
            return nil
        }
        
        if key.rawValue == "name" || key.rawValue == "address" {
            let textField = subviews?[0] as! NSTextField
            if value != nil {
                textField.stringValue = value as! String
            }
        }
        
        if key.rawValue == "gender" {
            let comboField = subviews?[0] as! NSComboBox
            if value != nil {
                comboField.stringValue = value as! String
            }
        }
        
        if key.rawValue == "married" {
            let checkBoxField = subviews?[0] as! NSButton
            checkBoxField.state = NSControl.StateValue(rawValue: 0)
            if (value != nil)  {
                checkBoxField.state = NSControl.StateValue(rawValue: 1)
            }
        }
        return view
    }
    
}

Bindings 表格数据

  1. 添加ArrayControl 控件,然后绑定到View Controller的一个变量中;
    在这里插入图片描述
    实现代码如下:
import Cocoa

class ViewController: NSViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        updateData()
    }

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }
    
    dynamic var datas = [NSDictionary]()
    
    func updateData() {
        self.datas = [
            ["name":"john","address":"USA"],
            ["name":"mary","address":"China"],
            ["name":"park","address":"Japan"],
            ["name":"Daba","address":"Russia"],
        ]
    }
}
  1. 添加一个NSTableView控件,设置tableColumn的identifier和上面的datas名称一样;
  2. 表格列绑定到Array Controller,Model Key Path可以省略;
  3. 单元格绑定到Model key path,选顶中的objectValue表示当前的行数据;
    在这里插入图片描述

动态编辑表格

在这里插入图片描述

协议实现

表格的操作,需要实现NSTableViewDataSource协议方法,否则无法保存数据:

    //动态编辑表格使用
    func tableView(_ tableView: NSTableView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, row: Int) {

        let data = self.datas[row]
        //表格列的标识
        let key = tableColumn?.identifier

        let editData = NSMutableDictionary.init(dictionary: data)

        editData[key!] = object

        self.datas[row] = editData

    }

添加和删除行

    @IBAction func addTableRow(_ sender: NSButton) {
        
        let data = NSMutableDictionary()
        data["name"] = ""
        data["address"] = ""
        
        //增加数据到datas数据区
        self.datas.append(data)
        //刷新表数据
        self.tableView.reloadData()
        //定位光标到新添加的行
        self.tableView.editColumn(0, row: self.datas.count-1 , with: nil, select: true)
    }
    
    @IBAction func deleteTableRow(_ sender: NSButton) {
        //表格当前选择的行
        let  row = self.tableView.selectedRow
        //如果row小于0表示没有选择行
        if row<0 {
            return
        }
        //从数据区删除选择的行的数据
        self.datas.remove(at: row)
        //刷新表数据
        self.tableView.reloadData()
    }

表格行拖放

  1. 注册一个自定义事件;
  2. 实现 NSTableViewDataSource 把表格行号数据拷贝到剪切板对象中;
  3. 实现 NSTableViewDataSource 把表格行号数据复制到表格对象中;
//注册拖放事件,事件为一自定义的值
let kTableViewDragDataTypeName = "TableViewDragDataTypeName"
self.tableView.register(forDraggedTypes: [kTableViewDragDataTypeName])

实现协议方法

    func tableView(_ tableView: NSTableView, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool {

        //将表格行号拷贝到剪切板对象中
        let zNSIndexSetData = NSKeyedArchiver.archivedData(withRootObject: rowIndexes);
        pboard.declareTypes([kTableViewDragDataTypeName], owner: self)
        pboard.setData(zNSIndexSetData, forType: kTableViewDragDataTypeName)
        return true
    }
        func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableViewDropOperation) -> Bool {
    
        let pboard = info.draggingPasteboard()
        let rowData = pboard.data(forType: kTableViewDragDataTypeName)
        let rowIndexes = NSKeyedUnarchiver.unarchiveObject(with: rowData!) as! NSIndexSet
        let dragRow = rowIndexes.firstIndex
        
        let temp = self.datas[row]
        self.datas[row] = self.datas[dragRow]
        self.datas[dragRow] = temp
        
        tableView.reloadData()
        return true
    }

表格事件

定义表格常见的一些操作事件

数据选取

以下是表格行和列的选取方法,采用(X,Y)坐标系的方式:

        let row = self.selectedRow
        let col = self.selectedColumn

行单击事件

需要实现 NSTableViewDelegate 协议中的方法,不需要设置绑定

    func tableViewSelectionDidChange(_ notification: Notification){
        let tableView = notification.object as! NSTableView
        let row = tableView.selectedRow
        print("selection row \(row)")
    }

行双击事件

动态添加事件。

        //表格双击事件
        self.tableView.doubleAction = #selector(ViewController.doubleAction(_:))
	    @IBAction func doubleAction(_ sender: AnyObject ) {
	        let row = self.tableView?.selectedRow
	        print("double selection row \(row!)")
	    }

表格右键菜单

  1. 拖动一个NSMenu到设计面板;
  2. 绑定menu对象到ViewController中;
  3. 实现NSMenuDelegate协议
@IBOutlet weak var tableMenu: NSMenu!
//表格菜单
self.tableView.menu = self.tableMenu
self.tableView.menu?.delegate = self

实现协议

//表格上下文菜单协议
extension ViewController: NSMenuDelegate {
    
    func menuNeedsUpdate(_ menu: NSMenu) {
        menu.removeAllItems()
        NSLog("menu clicked !")
    }
    
}

不同菜单的写法

import Cocoa

class MyTableView: NSTableView {
    var menu1: NSMenu?
    var menu2: NSMenu?
    override func menu(for event: NSEvent) -> NSMenu? {
        let row = self.selectedRow
        let col = self.selectedColumn
        if (row == 0 && col == 1) {
            return self.menu1
        }
        return  self.menu2
    }
}

表格排序

在这里插入图片描述

点击列头时出现排序升降箭头指示,其算法也可以自定义,在表格初始化后,可调用一次以下几个方法之一;

    //表格排序
    func tableSortConfig() {
        for tableColumn in self.tableView.tableColumns {
            //升序排序
            let sortRules = NSSortDescriptor(key: tableColumn.identifier.rawValue, ascending: true)
            tableColumn.sortDescriptorPrototype = sortRules
        }
    }
    
    func tableSortConfig2() {
        for tableColumn in self.tableView.tableColumns {
            //升序排序 使用字符串标准的比较函数
            let sortRules = NSSortDescriptor(key: tableColumn.identifier.rawValue, ascending: true, selector: #selector(NSString.localizedStandardCompare(_:)))
            
            tableColumn.sortDescriptorPrototype = sortRules
        }
    }
    
    func tableSortConfig3() {
        for tableColumn in self.tableView.tableColumns {
            //升序排序
            let sortRules = NSSortDescriptor(key: tableColumn.identifier.rawValue, ascending: true, comparator:{ s1,s2 in
                 let str1 = s1 as! String
                 let str2 = s2 as! String
                 if str1 > str2 {return .orderedAscending}
                 if str1 < str2 {return .orderedDescending}
                 return .orderedSame
                 }
            )
            
            tableColumn.sortDescriptorPrototype = sortRules
        }
    }

编码实现

其实就是按步骤创建下列元素。
在这里插入图片描述
下面的代码需要完成以下界面效果
在这里插入图片描述

创建表格元素

基本元素定义

    //1、定义表格对象和滚动条对象,最外层对象
    let tableView = NSTableView()
    let tableScrollView = NSScrollView()
    
    //2、定义存储表格数据的变量
    var datas = [NSDictionary]()

其组装过程大概如下:

    //3、组装表格
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //创建表格列
        self.tableViewConfig()
        //配置滚动条视图
        self.tableScrollViewConfig()
        //设置滚动条自动布局
        self.autoLayoutConfig()
        //加载更新数据
        self.updateData()
    }

创建表头

    func tableViewConfig() {
        self.tableView.focusRingType = .none
        //self.tableView.autoresizesSubviews = true
        
        self.tableView.delegate = self
        self.tableView.dataSource = self
        
        let column1 = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "name"))
        column1.title = "name"
        column1.width = 80
        column1.maxWidth = 100
        column1.minWidth = 50
        self.tableView.addTableColumn(column1)
        
        let column2 = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "address"))
        column2.title = "address"
        column2.width = 80
        column2.maxWidth = 100
        column2.minWidth = 50
        self.tableView.addTableColumn(column2)
    }

设置滚动条样式

这块可有可无

    func tableScrollViewConfig() {
        
        self.tableScrollView.hasVerticalScroller = true
        self.tableScrollView.hasVerticalScroller = false
        self.tableScrollView.focusRingType = .none
        self.tableScrollView.autohidesScrollers = true
        self.tableScrollView.borderType = .bezelBorder
        self.tableScrollView.translatesAutoresizingMaskIntoConstraints = false
    
        self.tableScrollView.documentView = self.tableView
        self.view.addSubview(self.tableScrollView)
    }

设置布局

此处建议,使用了自动化布局窗口辅助,此处使用的是Masony,但如果用swiftui需要引入三方包

    func autoLayoutConfig() {
            
       let topAnchor = self.tableScrollView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0)
       let bottomAnchor = self.tableScrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0)
        
       let leftAnchor =  self.tableScrollView.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 0)
       let rightAnchor = self.tableScrollView.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: 0)
        
       NSLayoutConstraint.activate([topAnchor, bottomAnchor, leftAnchor, rightAnchor])
    }

初始化数据

更新数据同时,需要实现表格代理协议

    func updateData() {
     
        self.datas = [
            ["name":"john","address":"USA"],
            ["name":"mary","address":"China"],
            ["name":"park","address":"Japan"],
            ["name":"Daba","address":"Russia"],
        ]
        
        self.tableView.reloadData()
    }

实现NSTableViewDelegate协议

这里有很多方法可以按需定义

extension ViewController: NSTableViewDelegate {
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        
        let data = self.datas[row]
        //表格列的标识
        let key = (tableColumn?.identifier)!
        //单元格数据
        let value = data[key]
        
        //根据表格列的标识,创建单元视图
        var view = tableView.makeView(withIdentifier: key, owner: self)
       
        if view == nil {
        
            let cellView = NSTableCellView()
            cellView.identifier = identifier;
            view = cellView
            
            let textField =  NSTextField()
            textField.translatesAutoresizingMaskIntoConstraints = false
            textField.isBezeled = false
            textField.drawsBackground = false

            cellView.addSubview(textField)
            
            let topAnchor = textField.topAnchor.constraint(equalTo: cellView.topAnchor, constant: 0)
            let bottomAnchor = textField.bottomAnchor.constraint(equalTo: cellView.bottomAnchor, constant: 0)
            
            let leftAnchor =  textField.leftAnchor.constraint(equalTo: cellView.leftAnchor, constant: 0)
            let rightAnchor = textField.rightAnchor.constraint(equalTo: cellView.rightAnchor, constant: 0)
            
            NSLayoutConstraint.activate([topAnchor,bottomAnchor,leftAnchor, rightAnchor])
            
        }
        
        let subviews = view?.subviews
        if (subviews?.count)!<=0 {
            return nil
        }
        
        let textField = subviews?[0] as! NSTextField

        if value != nil {
            textField.stringValue = value as! String
        }
        
        return view
    }
    
    
    func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
        return 30
    }
    
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/903342.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

控制流与循环:掌握程序的基本控制(2/10)

目录 控制流与循环&#xff1a;掌握程序的基本控制&#xff08;2/10&#xff09; 介绍 条件语句 基本用法 示例&#xff1a;判断用户输入的数字 条件语句中的逻辑运算符 示例&#xff1a;判断年龄阶段 循环结构 for 循环 示例 1&#xff1a;遍历列表 示例 2&#xf…

Python酷库之旅-第三方库Pandas(173)

目录 一、用法精讲 796、pandas.Float32Dtype类 796-1、语法 796-2、参数 796-3、功能 796-4、返回值 796-5、说明 796-6、用法 796-6-1、数据准备 796-6-2、代码示例 796-6-3、结果输出 797、pandas.Float64Dtype类 797-1、语法 797-2、参数 797-3、功能 797-…

linux查看系统架构的命令

两种方式&#xff0c;以下以中标麒麟为示例&#xff1a; 1.cat /proc/verison Linux version 3.10.0-862.ns7_4.016.mips64el mips64el即为架构 2.uname -a 输出所有内容 Linux infosec 3.10.0-862.ns7_4.016.mips64el #1 SMP PREEMPT Mon Sep 17 16:06:31 CST 2018 mips64el…

第J8周:Inception v1算法实战与解析

>- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营]中的学习记录博客** >- **&#x1f356; 原作者&#xff1a;[K同学啊]** &#x1f4cc; 本周任务&#xff1a; 1了解并学习图2中的卷积层运算量的计算过程&#xff08;&#x1f3d0;储备知识->卷积层运算…

内网穿透之网络层ICMP隧道

免责申明 本文仅是用于学习检测自己搭建的靶场环境有关ICMP隧道原理和攻击实验,请勿用在非法途径上,若将其用于非法目的,所造成的一切后果由您自行承担,产生的一切风险和后果与笔者无关;本文开始前请认真详细学习《‌中华人民共和国网络安全法》‌及其所在国家地区相关法规…

提升网站流量和自然排名的SEO基本知识与策略分析

内容概要 在当今数字化时代&#xff0c;SEO&#xff08;搜索引擎优化&#xff09;成为加强网站可见度和提升流量的重要工具。SEO的基础知识包括理解搜索引擎的工作原理&#xff0c;以及如何通过优化网站内容和结构来提高自然排名。白帽SEO和黑帽SEO代表了两种截然不同的策略&a…

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-27

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-27 目录 文章目录 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-27目录1. Large Language Model-based Augmentation for Imbalanced Node Classification on Text-Attributed Graphs摘要研究背…

耳背式助听器与定制式助听器,究竟该如何选?

在面对听力损失问题时&#xff0c;选择一款合适的助听器至关重要。目前&#xff0c;耳背式助听器和定制式助听器是比较常见的两种类型&#xff0c;很多人在二者之间犹豫不决。那么&#xff0c;到底应该怎么选呢&#xff1f; 一、耳背式助听器的特点 耳背式助听器形状类似香蕉&a…

论文阅读 - Pre-trained Online Contrastive Learning for Insurance Fraud Detection

Pre-trained Online Contrastive Learning for Insurance Fraud Detection| Proceedings of the AAAI Conference on Artificial Intelligence 目录 摘要 Introduction Methodology Problem Formulation Pre-trained Model for Enhanced Robustness Detecting Network a…

【STM32】程序建立模板

文章目录 STM32的开发方式建立基于库函数的工程建立工程的具体步骤具体程序举例工程架构 本篇介绍如何建立一个STM32工程 STM32工程结构比较复杂&#xff0c;需要用到的文件很多&#xff0c;并且程序代码也都是建立在工程结构的基础之上&#xff0c;所以学习如何新建一个STM32工…

Oracle视频基础1.1.4练习

1.1.4 dbb,ddabcPMON,SMON,LGWR,CKPT,DBWna5,b4,c2,d3,e1ad,a,c,b,eOracle instance,Oracle databaseSGA,background processcontrol file,data file,online redo file 以下是一篇关于 Oracle 基础习题 1.1.4 的博客&#xff1a; Oracle 基础习题解析&#xff1a;1.1.4 本篇文…

UE5 喷射背包

首选创建一个输入操作 然后在输入映射中添加&#xff0c;shift是向上飞&#xff0c;ctrl是向下飞 进入人物蓝图中编写逻辑&#xff0c;变量HaveJatpack默认true&#xff0c;Thrust为0 最后

linux进程的状态

​​​​​​​linux进程的概念 上篇我们学习了进程的概念&#xff0c;这篇我们将学习进程的状态 目录 前言 一、子进程和父进程 1、pid和ppid 2、通过系统调用创建进程-fork初识 二、进程的状态 1.Linux内核源代码 2.进程状态查看 3、Z(zombie)-僵尸进程 ​编辑 僵尸…

Linux下docker中elasticsearch与kibana的安装

他的脸红不是因为亚热带季风气候&#xff0c;而是因为那天太阳不忠&#xff0c;出卖一九九四年夏末心动。–《太平山顶》 在本篇博客中&#xff0c;我将详细介绍如何在 Linux 系统中安装并配置 Elasticsearch 和 Kibana&#xff0c;这两者是 ELK 堆栈的重要组成部分&#xff0c…

密钥管理方法DUKPT的OpenSSL代码实现Demo

目录 1 DUKPT简介 2 基本概念 2.1 BDK 2.2 KSN 2.3 IPEK 2.4 FK 2.5 TK 3 工作流程 3.1 密钥注入过程 3.2 交易过程 3.3 BDK派生IPEK过程 3.4 IPEK计算FK过程 4 演示Demo 4.1 开发环境 4.2 功能介绍 4.3 下载地址 5 在线工具 6 标准下载 1 DUKPT简介 DUKPT&a…

DEVOPS: 集群伸缩原理

概述 阿里云 K8S 集群的一个重要特性&#xff0c;是集群的节点可以动态的增加或减少有了这个特性&#xff0c;集群才能在计算资源不足的情况下扩容新的节点&#xff0c;同时也可以在资源利用 率降低的时候&#xff0c;释放节点以节省费用理解实现原理&#xff0c;在遇到问题的…

Linux系统解压分卷压缩文件的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

【CUDA代码实践03】m维网格n维线程块对二维矩阵的索引

文章目录 一、数据存储方式二、二维网格二维线程块三、二维网格一维线程块四、一维网格一维线程块 为了方便下次找到文章&#xff0c;也方便联系我给大家提供帮助&#xff0c;欢迎大家点赞&#x1f44d;、收藏&#x1f4c2;和关注&#x1f514;&#xff01;一起讨论技术问题&am…

低功耗4G模组:FTP应用示例

一、FTP 概述 FTP&#xff08;File Transfer Protocol&#xff0c;文件传输协议&#xff09; 是 TCP/IP 协议组中的协议之一。 FTP协议包括两个组成部分&#xff0c;其一为FTP服务器&#xff0c;其二为FTP客户端。 其中FTP服务器用来存储文件&#xff0c;用户可以使用FTP客户…

鸿蒙UI开发——基于组件安全区方案实现沉浸式界面

1、概 述 本文是接着上篇文章 鸿蒙UI开发——基于全屏方案实现沉浸式界面 的继续讨论。除了全屏方案实现沉浸式界面外&#xff0c;我们还可以使用组件安全区的方案。 当我们没有使用setWindowLayoutFullScreen()接口设置窗口为全屏布局时&#xff0c;默认使用的策略就是组件安…