#2 UIView 相关问题集合

3 UIViewController

3.1什么是UIViewController?

iOS中经常看到MVC模式,因此代码中有的充当M,有的充当V,有的充当C的角色,但是 UIViewController 既有 View,又有 Controller,那么他到底是什么呢?

这个问题没有唯一答案,在iPhone早期, UIViewController 表示一屏的内容,例如,你的邮箱是一个视图控制器,当你阅读某条邮件,将显示另一个不同的视图控制器。

但是真实情况远比这个复杂,因为视图控制器的容器性,你可以将一个视图控制器放到另一个视图控制器中。结果,一屏的内容可能包含多个视图控制器一起协同工作。

视图控制器主要的不可推脱的角色是 响应视图生命周期事件。即当你的视图控制器将在视图被创建,显示,隐藏和销毁时被调用,你可以利用这些生命周期来实现自己的逻辑。

有些人将他们的视图控制器更偏向于视图部分(例如,处理布局),一些人则更偏向控制部分(例如,将布局代码放在 UIView 子类中,将粘合剂代码放在视图控制器中),还有一些人同时做2项(将视图代码和控制器代码放在同一个地方)。

3.2 如何使用视图控制器容器?

视图控制器容器允许你在一个视图控制器中插入另一个视图控制器,这能够简化和有利于你组织代码。可以按照下面4步:

  1. 在你的父视图控制器中调用 addChild(),参数是子视图控制器
  2. 如果你使用frames,则设置你需要的子视图控制器的frame
  3. 将子视图添加到主视图中,添加自动布局约束
  4. 在子视图控制器中调用 didMove(toParentViewController:),参数使用你的主视图控制器
addChild(child)
child.view.frame = view.frame
view.addSubview(child.view)
child.didMove(toParent: self)

当你完成了上面这些,下面步骤理论上相似,但是是相反的:

  1. 调用 willMove(toParent:), 传入 nil
  2. 从父视图控制器中移除子视图控制器
  3. 在子视图控制器中调用 removeFromParent()
willMove(toParent: nil)
view.removeFromSuperview()
removeFromParent()

为了方便,你可以考虑给 UIViewController 添加一个小的,私有的扩展来帮你完成这项任务,注意你需要按照顺序来运行,否则很容易出错:

// @nonobjc 用来避免和iOS自己的代码产生冲突
@nonobjc extension UIViewController {
    func add(_ child: UIViewController, frame: CGRect? = nil) {
        addChild(child)
        
        if let frame = frame {
            child.view.frame = view.frame
        }
        
        view.addSubview(child.view)
        child.didMove(toParent: self)
    }
    
    func remove() {
        willMove(toParent: nil)
        view.removeFromSuperview()
        removeFromParent()
    }
}

注意这里的代码的方法名和原文的不一样,此处使用的swift版本是4.2

示例:

// 创建一个子视图控制器
import UIKit
class ChildViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 创建一个视图控件
        let bg = UIView()
        bg.backgroundColor = .yellow
        bg.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(bg)
        
        NSLayoutConstraint.activate([
            bg.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20),
            bg.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20),
            bg.heightAnchor.constraint(equalToConstant: 200),
            bg.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor)
        ])

        // Do any additional setup after loading the view.
    }
}

将子视图控制器添加到父视图控制器中:

// 父视图控制器
import UIKit

class ViewController: UIViewController {
    
    let child = ChildViewController()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .orange
        addChild(child)
        child.view.frame = view.frame
        view.addSubview(child.view)
        child.didMove(toParent: self)
        
    }

}

4.UIView

4.1 如何强制UIView重绘: setNeedsDisplay()

所有的视图及其子类都是使用 drawRect() 方法进行渲染的,但是你不该直接自己调用这个方法。实际上,它是由系统在需要绘制时调用的,这可以避免多次绘制的发生。

但是,如果你想要一个视图立即重绘,你应该像这样调用 setNeedsDisplay():

myButton.setNeedsDisplay()

这个方法将会通知UIKit使用 drawRect() 重绘该button,但是需要重绘(redraw)不在队列中。

4.2 如何使用一个UIView遮挡另一个UIView?

所有的views都有一个 mask (蒙版)属性,可以让你依据情况剪切部分视图。这个mask可以是任意的UIView,例如,用一个label遮挡一个image view

let redView = UIView(frame: CGRect(x: 50, y: 50, width: 128, height: 128))
redView.backgroundColor = .red
view.addSubview(redView)

接着创建一个mask作为单独的UIView。可能需要给视图一个背景色或者某些内容,因为mask alpha通道到决定原始视图上显示什么。

下面是一个和原始视图相同尺寸的mask,但是它向右偏移了64像素,以及一个64point的圆角。当用作先前视图的mask时,它会出现一个半圆的效果

// 注意这里的 x: 64 表示的是向右偏移64
let maskView = UIView(frame: CGRect(x: 64, y: 0, width: 128, height: 128))
maskView.backgroundColor = .blue
maskView.layer.cornerRadius = 64
redView.mask = maskView

蓝色背景并不可见,它只是用来当做遮挡的蒙版。

4.3 如何使用removeFromSuperview()从父视图中移除一个UIView?

如果你动态的创建一个视图,然后想移除,可以直接调用 removeFromSuperview() 方法。当你调用时,视图会立即被移除,可能被销毁(如果存在引用,可能不会被销毁):

yourView.removeFromSuperview()

4.4 如何把一个子视图放在一个UIView的前面?

UIKit 从后向前绘制视图,这表示在栈中比较高的视图将绘制在其它视图的上面。如果你想要把一个视图放在前面,可以使用 bringSubviewToFront(_ view: UIView):

parentView.bringSubviewToFront(childView)

这个方法可以将任何子视图放在前面,即使你不确定它在哪里:

childView.superView?.bringSubviewToFront(childView)

注意,此处使用的是swift4.2,和原文中的API有所不同

示例:

import UIKit
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
       let orangeView = UIView(frame: CGRect(x: 64, y: 100, width: 128, height: 128))
        orangeView.backgroundColor = .orange
        view.addSubview(orangeView)
        
        // 蓝色视图在栈的上面
        // 因此会遮挡住上面的橘色视图
        let blueView = UIView(frame: CGRect(x: 64, y: 100, width: 200, height: 200))
        blueView.backgroundColor = .blue
        view.addSubview(blueView)
 
// 使用下面方法将橘色视图放在父视图的最前面
//        view.bringSubviewToFront(orangeView)
        orangeView.superview?.bringSubviewToFront(orangeView)
    }
}

4.5 如何使用自动布局锚点让一个UIView填充整个屏幕?

你可以将4个角和父视图容器的4个角对齐来填充整个屏幕,下面是扩展:

extension UIView {
    func pinEdges(to ohter: UIView) {
        leadingAnchor.constraint(equalTo, other.leadingAnchor).isActive = true
        trailingAnchor.constraint(equalTo: other.trailingAnchor).isActive = true
        topAnchor.constraint(equalTo: other.topAnchor).isActive = true
        bottomAnchor.constraint(equalTo: other.bottomAnchor).isActive = true
    }
}

然后可以这样调用 pinEdges(to: someOtherView).

4.6 如何设置UIView的着色?

tintColor 属性可以改变着色效果,具体效果要看控件的类型:对导航条和tab bars表示的是按钮上的文字和图标,对文字视图表示选择光标和高亮文字,对进度条表示的是track的颜色。

tintColor 可以单独给某个视图设置颜色,对在视图控制器中的所有视图,甚至真个应用窗口都可以一次性设置颜色。

设置当前视图控制器的颜色,可以使用下面代码:

override func viewDidLoad() {
    view.tintColor = .red
}

如果你想应用中所有的视图进行着色,可以在 AppDelegate.swift:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    window?.tintColor = UIColor.red
    return true;
}

示例:

import UIKit

class ViewController: UIViewController {
    
    let child = ChildViewController()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
//       let orangeView = UIView(frame: CGRect(x: 64, y: 100, width: 128, height: 128))
//        orangeView.backgroundColor = .orange
//        view.addSubview(orangeView)
//        
//        let blueView = UIView(frame: CGRect(x: 64, y: 100, width: 200, height: 200))
//        blueView.backgroundColor = .blue
//        view.addSubview(blueView)
//        
////        view.bringSubviewToFront(orangeView)
//        orangeView.superview?.bringSubviewToFront(orangeView)
        
        
        let button = UIButton(type: .system)
        button.setTitle("hello world", for: .normal)
        view.addSubview(button)
        
        button.translatesAutoresizingMaskIntoConstraints = false
        // 将button居中
        button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true

        // 改变tintColor的颜色
        // 会发现button内文字的颜色也变成了红色
        view.tintColor = .red
    }


}

4.7 如何使用 viewWithTag() 找到一个UIView的子视图?

如果你想快速的从一个复杂层级的视图内获取一个视图引用,可以使用 viewWithTag(_ tag: Int) 方法,这个方法会搜索所有的子视图,以及子子视图,直到找到匹配的tag number.这个方法返回一个 UIView?类型,因此使用时要注意解包:

if let foundView = view.viewWithTag(0xDEADBEEF) {
    // 将找到的视图从父视图中移除
    foundView.removeFromSuperview()
}

tags 例如0xDEADBEEF 是很常见的coders。这个方法只是偶尔使用的一种捷径,不要在开发中依赖这种方法。

4.8 如何给UIView添加一个阴影?

iOS可以动态的给任何UIView添加阴影,这些阴影会自动的适配item的形状,甚至能够沿着UILabel内文字的曲线。

  • shadowColor: 设置阴影的颜色,需要是一个 CGColor 类型
  • shadowOpacity: 阴影的透明度,0表示不可见,1表示最强
  • shadowOffset:阴影的偏移,给一种3D偏移效果
  • shadowRadius: 设置阴影的宽度

例如:

yourView.layer.shadowColor = UIColor.black.cgColor
yourView.layer.shadowOpactiy = 1
yourView.layer.shadowOffset = CGSize.zero
yourView.layer.shadowRadius = 10

动态的产生阴影是很消耗内存的,因为iOS必须按照视图形状绘制。如果可以的话,可以将shadowPath 设置 为一个具体值,这样iOS不需要动态计算透明度。例如,下面创建一个等于view frame的阴影:

yourView.layer.shadowPath = UIBezierPath(rect: yourView.bounds).cgPath

另外可以告诉ios缓存渲染的阴影,这样避免重复绘制:

yourView.layer.shouldRasterize = true

4.9 如何使用 convert() 将一个UIView里面的CGPoint转换到另一个视图中?

每一个视图都有自己的坐标系统,这意味着如果我点击一个按钮,询问iOS我点在哪里了,它将告诉我按钮相对于左上角的位置。但如果您想将一个视图中的位置转换为一个位置,那么这很容易做到。

例如,下面代码创建2个视图,创建一个虚拟的点击,然后将其从第一个视图坐标空间转换到第二个视图中:

let view1 = UIView(frame: CGRect(x: 50, y: 50, width: 128, height: 128))
let view2 = UIView(frame: CGRect(x: 200, y: 200, width: 128, height: 128))

let tap = CGPoint(x: 10, y: 10)
// convertedTap 将变为 (-140.0,-140.0)
let convertedTap = view1.convert(tap, to: view2)

4.10 如何使用CGAffineTransform对UIView进行缩放,伸展,移动和旋转?

每一个 UIView 都有一个 transform 属性,可以用来操控视图的尺寸,位置和旋转(使用affine transform)。这个属性是可动画的,这意味着可以改变某个值,使一个视图平滑的变大变小,旋转。

// double view size
imageView.transform = CGAffineTransform(scaleX: 2, y: 2)
// 移动到左边256位置
imageView.transform = CGAffineTransform(translateX: -256, y: -256)
// 旋转180度
imageView.transform = CGAffineTransform(rotationAngle: CGFloat.pi)

// 恢复成原样
imageView.transform = CGAffineTransform.identity

动画部分待学习

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,960评论 3 119
  • 在一个稍大的项目中,通常会有上百个组件,如果这些组件采用xml的bean定义来配置,显然会增加配置文件的体积,查找...
    二月惊蛰阅读 280评论 0 0
  • 上一篇网贷平台Prosper2005~2014贷款数据分析(一)中,主要重要变量的介绍、几个重要变量的转换、数据的...
    YoLean阅读 2,972评论 2 6
  • 今天,我坐在电视机前,用了三个小时的时间,看完了一部电影,你们一定都听过,《辛德勒的名单》。 在电影的开头,我还搞...
    特别黑的黑洞阅读 161评论 0 0
  • 看清楚哦~~这是Sublime text 3不是2的版本,两者的安装还是有区别的,下面的方法是我感觉比较简单的,其...
    MarkusFeng阅读 503评论 0 0