【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记24 popovers弹窗

简介: 上几话中我们详细了解了几种segue,我们也了解到了多MVC模式的几种控制器,比如导航、选项卡和分栏,除了这三种多MVC的模式之外,还有一种popover,它跟其他三种不太一样。

上几话中我们详细了解了几种segue,我们也了解到了多MVC模式的几种控制器,比如导航、选项卡和分栏,除了这三种多MVC的模式之外,还有一种popover,它跟其他三种不太一样。首先先来认识一下popover(弹窗)


你可以看到弹窗会有一个小箭头指向触发弹窗的地方:


它像一个白色的三角形。出了弹窗的区域是白色,其他区域都是灰色的,单击其他区域的唯一功能就是让弹窗消失。


说popover不同的原因是,它不是一个UIViewController。它通常是presentation controller来出现在屏幕上的。所以popover并不真的需要一个viewcontroller,它的view是这个MVC,它可以纯粹依靠presentation controller机制来做到这一点。

虽然它不是自己的viewcontroller,但是它依旧有所有的segue,用法并没有区别。我们刚才在示例中看到的弹窗是ipad上的效果,在iphone它被modal替代了,IOS自动为你配适的。但是如果你使用代理或者presentation controller,你可以影响这个配适。我们来看幻灯片:


绿色部分和其他segue没有什么区别,但是黄色这行我从viewcontroller 过渡到popover得presentationcontroller。当你设置自身为代理时,你能做些什么呢?



我们看到代理中有两个代理方法。第一个方法用来配适设备,默认iphone上全屏展示,如果你把它的返回值的风格设为none,表示不配适,那么它的弹窗会和iphone上一样。

弹窗的另外一个重点是尺寸,你可能需要用一种面向对象的方式,也就是系统调用的方式来询问MVC合适的尺寸是多少,这只是控制器的一个属性,你可以重写它:


下面来展示一个Demo,让我们的弹窗显示浏览历史,并且适应内容的尺寸。

我们回到Psychologist这个Demo中,在storyboard中给HappinessVeiwController右上角添加一个按钮History用来显示我们点击的按钮的值,这些值组成整数数组用来表达小人脸的开心程度。注意这个按钮不要用UIButton,用BarButtonItem,这是个轻量级的按钮,专门放置在导航栏或者工具栏上。


我们需要让这个按钮展示一个新的控制器,所以我们向storyboard中拖一个新的控制器,然后把History按钮和这个控制器连线,注意segue方式要选择popover present。


和其他segue一样,给Identifier命名,我们取名为Show Diagnostic History。

虽然现在控制器是空白的,但是我们已经可以运行了。我们创建一个UIViewController和这个控制器对应起来,取名为TextViewController。

在storyboard中拖一个text view到新控制器中,这个textview可以显示多行文本,设置它为不可编辑,但是可以选中,修改文本文字为24号。你会在storyboard中看到textview中有很多文字,这些是占位文字,没有关系我们会在运行的时候重新写值,这些占位文字是不会显示的。


我们在代码中创建多行文本的outlet。

import UIKit

class TextViewController: UIViewController {

    @IBOutlet weak var textView: UITextView!{
        didSet{
        textView.text = text
        }
    }
 
    var text:String = ""{
        didSet{
        textView?.text = text
        }
    
    }
}

现在该为我们的segue做些准备了。那么这些准备工作应该在哪里做呢?显然我们不应该在HappinessViewController中做,因为这个控制器你可能是从别处拷贝来的,它的设计者希望它是专门用来管理笑脸的,它应该对浏览历史一无所知。那么我们该如何做呢?答案是创建一个新的控制器,然后继承HappinessViewController,再在其中增加浏览历史的功能。

import UIKit

class DiagonsedHappinessViewController: HappinessViewController {



}

那么现在回到storyboard中,笑脸的类应该不再是HappinessViewController了,而是我们刚刚修改的新的类。



我们之前设置的各种outlet不会有问题,因为它是子类,继承了父类的所有东西,包括outlet。这就是控制器的多态性,通常你会有一个可重用的控制器,也许你想给某个特定的控制器中增加功能,这样你就可以创建它的子类。

import UIKit

class DiagonsedHappinessViewController: HappinessViewController {
    override var happiness:Int{
        didSet{
        diagnostHistory += [happiness]
        }
    }
   var diagnostHistory = [Int]()
    
    private struct History{
    static let SegueIdentifier = "Show Diagnostic History"
    
    }
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if let identifier = segue.identifier{
            switch identifier{
            case History.SegueIdentifier:
                if let tcv = segue.destinationViewController as? TextViewController{
                tcv.text = "\(diagnostHistory)"
                }
            default:break
                
            }
        
        }
        
    }

}


我们重写了属性happiness,这里的属性观察器和父类中的观察器不会冲突,程序会先执行父类中happiness的观察器运行你会发现这个记录只能记录上一次的点击记录,这是因为我们之前讲过的使用segue每次打开的MVC都是新创建的,所以这个浏览记录需要存在我们之前讲过的NSUserDefaults中。我们把diagnosticHistory改成计算属性,靠它读取或者写入NSDuserDefaults。

新的代码:

import UIKit

class DiagonsedHappinessViewController: HappinessViewController {
    override var happiness:Int{
        didSet{
        diagnostHistory += [happiness]
        }
    }
    private let defaults = NSUserDefaults.standardUserDefaults()
    var diagnostHistory:[Int]{
        get{return defaults.objectForKey(History.DefaultsKey) as? [Int] ?? []}
        set{defaults.setObject(newValue, forKey: History.DefaultsKey)}
    
    }
    
    private struct History{
    static let SegueIdentifier = "Show Diagnostic History"
    static let DefaultsKey = "DiagnosedHappinessViewController.History"
    }
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if let identifier = segue.identifier{
            switch identifier{
            case History.SegueIdentifier:
                if let tcv = segue.destinationViewController as? TextViewController{
                tcv.text = "\(diagnostHistory)"
                }
            default:break
                
            }
        
        }
        
    }

}

来运行一下看看,我们看到在iphone6上虽然出现了浏览记录,但是popover依旧会布满屏幕,下一个任务是修改它的尺寸了。


首先我们需要让我们的控制器可以作为自己的弹窗代理,所以:

class DiagonsedHappinessViewController: HappinessViewController,UIPopoverControllerDelegate 

然后如我们之前讲的,在segue中做处理,把原来的语句修改如下:

 case History.SegueIdentifier:
                if let tvc = segue.destinationViewController as? TextViewController,let ppc = tvc.popoverPresentationController {
                    ppc.delegate = self
                tvc.text = "\(diagnostHistory)"
                }

可以看到popoverPresentationController是在UIViewController中的,只当这个MVC真的在一个弹窗中的时候它才会返回,否则返回nil,然后我们把代理设为自己,这是我们第一次控制系统的代理。之后我们实现控制尺寸的代理方法;

    func adaptivePresentationStyleForPresentationController(controller: UIPresentationController!, traitCollection: UITraitCollection!) -> UIModalPresentationStyle {
        return UIModalPresentationStyle.None
    }

这个代理的返回表示我们不做任何配适,运行看看:


完整代码如下:

import UIKit

class DiagonsedHappinessViewController: HappinessViewController,UIPopoverPresentationControllerDelegate{
    override var happiness:Int{
        didSet{
        diagnostHistory += [happiness]
        }
    }
    private let defaults = NSUserDefaults.standardUserDefaults()
    var diagnostHistory:[Int]{
        get{return defaults.objectForKey(History.DefaultsKey) as? [Int] ?? []}
        set{defaults.setObject(newValue, forKey: History.DefaultsKey)}
    
    }
    
    private struct History{
    static let SegueIdentifier = "Show Diagnostic History"
    static let DefaultsKey = "DiagnosedHappinessViewController.History"
    }
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if let identifier = segue.identifier{
            switch identifier{
            case History.SegueIdentifier:
                if let tvc = segue.destinationViewController as? TextViewController,let ppc = tvc.popoverPresentationController {
                    ppc.delegate = self
                tvc.text = "\(diagnostHistory)"
                }
            default:break
                
            }
        
        }
     
    }
    func adaptivePresentationStyleForPresentationController(controller: UIPresentationController!, traitCollection: UITraitCollection!) -> UIModalPresentationStyle {
        return UIModalPresentationStyle.None
    }
    
}

现在还有最后一个步骤,弹窗的窗口有点大,我们需要它的尺寸适应弹窗中的内容的大小。这次我们需要到TextViewController中去做修改:

 override var preferredContentSize:CGSize {
        get{if textView != nil && presentingViewController != nil{//presentationViewController也是父类的属性,表示当前显示的页面
            return textView.sizeThatFits(presentingViewController!.view.bounds.size)
            
        } else {
            return super.preferredContentSize //无论如何要考虑所有情况
            }
        }
        set{super.preferredContentSize = newValue}
    }

我们重写父类中的这个属性

然后再次运行:


成功了!



目录
相关文章
|
前端开发 API
【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记4 MVC enum Tuple Dictionary
 回顾一下我们上一话中的代码: @IBAction func operate(sender: UIButton) { let operation = sender.
773 0
|
前端开发 iOS开发
【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记3 Xcode、Auto Layout及MVC
   继续上一话中的计算器Demo,上一话讲到类必须被初始化,类中的属性也必须被初始化,所以你不能只声明而不给它一个处置,那么问题来了,我们从storyboard中拖拽的@IBOutlet为什么只有声明而不需要初始化呢,这是因为它的类型依旧是一个optional,在你初始化之前已经被赋值为nil了,这也就是为什么你不需要再初始化它的原因。
846 0
|
Unix iOS开发
【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记1 IOS8概述
  首先感谢网易公开课和SwiftV课堂的朋友们辛苦翻译,这个系列是我学习斯坦福IOS8公开课的个人心得体会和笔记,希望能给大家带来启发。
1025 0
|
9天前
|
API 数据安全/隐私保护 iOS开发
利用uni-app 开发的iOS app 发布到App Store全流程
利用uni-app 开发的iOS app 发布到App Store全流程
116 3
|
9天前
|
存储 iOS开发
iOS 开发,如何进行应用的本地化(Localization)?
iOS 开发,如何进行应用的本地化(Localization)?
129 2
|
9天前
|
存储 数据建模 数据库
IOS开发数据存储:什么是 UserDefaults?有哪些替代方案?
IOS开发数据存储:什么是 UserDefaults?有哪些替代方案?
48 0
|
9天前
|
API 开发工具 Android开发
iOS 和 Android 平台的开发有哪些主要区别?
iOS与Android开发区别:iOS用Objective-C/Swift,App Store唯一下载渠道;Android用Java/Kotlin,多商店发布(如Google Play、华为市场)。设计上,iOS简洁一致,Android灵活可定制。开发工具,iOS用Xcode,Android用Android Studio。硬件和系统多样性,iOS统一,Android复杂。权限管理、审核流程及API各有特点,开发者需依据目标平台特性进行选择。
47 3
|
9天前
|
前端开发 Android开发 iOS开发
【Flutter前端技术开发专栏】Flutter在Android与iOS上的性能对比
【4月更文挑战第30天】Flutter 框架实现跨平台移动应用,通过一致的 UI 渲染(Skia 引擎)、热重载功能和响应式框架提高开发效率和用户体验。然而,Android 和 iOS 的系统差异、渲染机制及编译过程影响性能。性能对比显示,iOS 可能因硬件优化提供更流畅体验,而 Android 更具灵活性和广泛硬件支持。开发者可采用代码、资源优化和特定平台优化策略,利用性能分析工具提升应用性能。
【Flutter前端技术开发专栏】Flutter在Android与iOS上的性能对比
|
9天前
|
存储 Swift iOS开发
使用Swift开发一个简单的iOS应用的详细步骤。
使用Swift开发iOS应用的步骤包括:创建Xcode项目,设计界面(Storyboard或代码),定义数据模型,实现业务逻辑,连接界面和逻辑,处理数据存储(如Core Data),添加网络请求(必要时),调试与测试,根据测试结果优化改进,最后提交至App Store或其它平台发布。
40 0
|
9天前
|
安全 Swift iOS开发
【Swift 开发专栏】Swift 与 UIKit:构建 iOS 应用界面
【4月更文挑战第30天】本文探讨了Swift和UIKit在构建iOS应用界面的关键技术和实践方法。Swift的简洁语法、类型安全和高效编程模型,加上与UIKit的紧密集成,使开发者能便捷地创建用户界面。UIKit提供视图、控制器、布局、动画和事件处理等功能,支持灵活的界面设计。实践中,遵循设计原则,合理组织视图层次,运用布局和动画,以及实现响应式设计,能提升界面质量和用户体验。文章通过登录、列表和详情界面的实际案例展示了Swift与UIKit的结合应用。