美文网首页
#2 UIView 相关问题集合

#2 UIView 相关问题集合

作者: JamesSawyer | 来源:发表于2018-10-14 16:46 被阅读40次

    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
    

    动画部分待学习

    相关文章

      网友评论

          本文标题:#2 UIView 相关问题集合

          本文链接:https://www.haomeiwen.com/subject/tfftzftx.html