获取uiview的uiviewcontroller和rootviewcontroller

我们知道,对于一个视图控制器类UIViewController,可以通过[self view]来获取到它所管理的视图UIView,反过来,对于一个视图类UIView,我们怎么获取到它的UIViewController?

       UIView继承自UIResponder,UIResponder有一个实例方法- (UIResponder *)nextResponder,该方法返回响应链(responder chain)上的下一个对象。

       有关- (UIResponder *)nextResponder方法,更具体的说明可参考UIResponder Class Reference,主要有几个重点:

       1)UIResponder类不会自动存储和设置它的下一个响应者(the next responder),默认情况下nextResponder方法放回nil;

       2)UIResponder的子类必须重载nextResponder方法,设置下一个响应者。比如UIView类,当管理它的UIViewController对象存在时,返回该UIViewController,否则返回它的父视图(superview);UIViewController类返回它所管理的视图的父视图;UIWindow类返回应用对象(the application object);UIApplication类返回nil。


      一、获取view的controller对象:

  通过以上的分析,对于一个视图类UIView,我们可以利用- (UIResponder *)nextResponder方法遍历响应链,获取到它的UIViewController,具体代码:

[plain] view plaincopy
  1. @implementation UIView (FindUIViewController)  
  2.   
  3. - (UIViewController *)viewController {  
  4.     /// Finds the view's view controller.  
  5.       
  6.     // Traverse responder chain. Return first found view controller, which will be the view's view controller.  
  7.     UIResponder *responder = self;  
  8.     while ((responder = [responder nextResponder]))  
  9.         if ([responder isKindOfClass: [UIViewController class]])  
  10.             return (UIViewController *)responder;  
  11.       
  12.     // If the view controller isn't found, return nil.  
  13.     return nil;  
  14. }  
  15.   
  16. @end  


      二、获取rootViewcontroller对象:
[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. - (UIViewController *)getCurrentRootViewController {  
  2.   
  3.     UIViewController *result;  
  4.   
  5.     if (rootViewController)  
  6.     {  
  7.         // If developer provieded a root view controler, use it  
  8.         result = rootViewController;  
  9.     }  
  10.     else  
  11.     {  
  12.         // Try to find the root view controller programmically  
  13.   
  14.         // Find the top window (that is not an alert view or other window)  
  15.         UIWindow *topWindow = [[UIApplication sharedApplication] keyWindow];  
  16.         if (topWindow.windowLevel != UIWindowLevelNormal)  
  17.         {  
  18.             NSArray *windows = [[UIApplication sharedApplication] windows];  
  19.             for(topWindow in windows)  
  20.             {  
  21.                 if (topWindow.windowLevel == UIWindowLevelNormal)  
  22.                     break;  
  23.             }  
  24.         }  
  25.   
  26.         UIView *rootView = [[topWindow subviews] objectAtIndex:0];    
  27.         id nextResponder = [rootView nextResponder];  
  28.   
  29.         if ([nextResponder isKindOfClass:[UIViewController class]])  
  30.             result = nextResponder;  
  31.         else if ([topWindow respondsToSelector:@selector(rootViewController)] && topWindow.rootViewController != nil)  
  32.             result = topWindow.rootViewController;  
  33.         else  
  34.             NSAssert(NO, @"ShareKit: Could not find a root view controller.  You can assign one manually by calling [[SHK currentHelper] setRootViewController:YOURROOTVIEWCONTROLLER].");  
  35.     }  
  36.     return result;      
  37. }  

      三、让子类和父类都接收事件
当然, 你可能会说直接调用mysubview.superView即可, 这样做也确实是可以做到,但有时子view是不一定知道有这个特定的父view的存在的,如动态添加子view。

所以这里就可以用到消息响应链拉技术。

下面要做的也就是,让子view接收这些事件后,同时把这些事件继续向上传,会一直传到UIApplication为止。 而在传的过程中,如果子view接收了这些事件,那么事件会自然终止,我们现在可以做的是同时让子view接收事件,而且还让事件不终止,并继续向上传。

摘取一部分说明:

当用户  与  iPhone的触摸屏  产生  互动时,硬件  就会探测到  物理接触  并且  通知  操作系统。接着  操作系统  就会创建  相应的事件  并且  将  其  传递给  当前正在运行的应用程序的事件队列。然后  这项事件  会被事件循环  传递给  优先响应者物件。优先响应者物件  是  事件  被触发时  和  用户  交互的物件,比如  按钮物件、视图物件。如果  我们  编写了  代码  让  优先响应者  处理  这种类型的事件,那么  它  就会处理  这种类型的事件。处理完  某项事件后,响应者  有  两个选项:1、将  其  丢弃;2、将  其  传递给  响应链条中的下一个响应者。下一个响应者的地址   存储  在当前响应者物件所包含的变量nextResponder当中。如果  优先响应者  无法处理  一项事件,那么  这项事件  就传递给  下一个响应者,直到  这项事件  到达  能处理它的响应者  或者  到达  响应链条的末端,也就是  UIApplication类型的物件。UIApplication类型的物件  收到  一项事件后,也是  要么  处理,要么  丢弃。

比如  有  一个视图物件,这个视图物件上  有  一个按钮物件。当用户  触摸  这个按钮物件时,作为优先响应者,这个按钮物件  就会收到  一项事件。如果  这个按钮物件  无法处理  这项事件,就会将  这项事件  传递给  视图物件。如果  视图物件  无法处理  这项事件,就会将  这项事件  传递给  视图控制器物件。以此类推。

应该注意的  是  当我们  在使用  响应链条时,一项事件  并不会自动地  从一个响应者  传递到  下一个响应者。如果  要将  一项事件  从一个响应者  传递到  下一个响应者,我们  必须编写  代码  才能办到。

要做的如下:

view的代码如下:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

{

// 这里可以做子view自己想做的事,做完后,事件继续上传,就可以让其父类,甚至父viewcontroller获取到这个事件了

[[selfnextResponder]touchesBegan:toucheswithEvent:event];

}


- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

{

[[selfnextResponder]touchesEnded:toucheswithEvent:event];

}


- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

{

[[selfnextRespondertouchesCancelled:toucheswithEvent:event];

}


- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event

{

[[selfnextRespondertouchesMoved:toucheswithEvent:event];

}

另外需要注意的是:在重写这几个方法时,最好保证这几个方法都重写,否则事件响应链可能会变混乱。这是我的猜测哈,没有实际验证过。
<think>我们正在使用Delphi 11开发iOS应用,并希望在UIViewController中禁用分享下载功能。根据之前的讨论,我们主要关注的是在TQLPreviewController中禁用分享下载,但用户现在的问题是如何在一般的UIViewController中设置禁止分享下载。然而,由于UIViewController本身并不直接提供分享下载功能,通常这些功能是由其子控件(如UIWebView、WKWebView、QLPreviewController等)提供的。因此,我们需要根据具体的场景来实施不同的策略。 根据引用[1][3],我们可以通过设置视图控制器的呈现样式(modalPresentationStyle)来影响其显示,但这对禁用分享下载没有直接帮助。因此,我们需要针对不同的内容展示方式采取不同的方法。 以下是针对不同场景的解决方案: 1. **如果使用QLPreviewController(用于预览文件)**: 我们已经在前面的回答中提供了详细的方案,即通过移除导航栏的分享按钮、使用临时文件以及设置代理来阻止用户操作。 2. **如果使用WebView(显示网页或PDF)**: 我们可以通过设置WebView的属性来禁用长按菜单(其中包含分享下载选项)以及禁用链接点击。 3. **如果使用自定义视图**: 则需要在自定义视图中避免使用可以触发分享或下载的控件。 下面我们分别给出具体实现: ### 场景1:在QLPreviewController中禁用分享下载(回顾) 参考之前的回答,我们通过以下步骤实现: - 移除导航栏的分享按钮(通过设置`rightBarButtonItem`为nil)。 - 使用临时文件,并在退出后删除。 - 设置`QLPreviewControllerDelegate`,并阻止所有操作。 代码示例: ```pascal uses Macapi.ObjectiveC, iOSapi.UIKit, iOSapi.QuickLook, System.IOUtils; type TSecurePreviewControllerDelegate = class(TOCLocal, QLPreviewControllerDelegate) public function previewController(controller: QLPreviewController; shouldOpenURL: NSURL; forPreviewItem: QLPreviewItem): Boolean; cdecl; function previewController(controller: QLPreviewController; handleActionForPreviewItem: QLPreviewItem; action: SEL): Boolean; cdecl; end; function TSecurePreviewControllerDelegate.previewController(controller: QLPreviewController; shouldOpenURL: NSURL; forPreviewItem: QLPreviewItem): Boolean; begin Result := False; // 阻止通过URL打开 end; function TSecurePreviewControllerDelegate.previewController(controller: QLPreviewController; handleActionForPreviewItem: QLPreviewItem; action: SEL): Boolean; begin Result := False; // 阻止所有用户操作 end; procedure ShowSecurePreview(AFileName: string); var PreviewController: QLPreviewController; NavController: UINavigationController; TempPath: string; Delegate: TSecurePreviewControllerDelegate; begin // 复制到临时目录 TempPath := TPath.GetTempPath + '/tempfile.pdf'; TFile.Copy(AFileName, TempPath, True); PreviewController := TQLPreviewController.Wrap(TQLPreviewController.Alloc.init); // 设置数据源(这里需要实现QLPreviewControllerDataSource,略) // PreviewController.setDataSource(...); // 设置代理 Delegate := TSecurePreviewControllerDelegate.Create; PreviewController.setDelegate(Delegate.GetObjectID); // 移除分享按钮 PreviewController.navigationItem.setRightBarButtonItem(nil, False); // 嵌入导航控制器 NavController := TUINavigationController.Wrap(TUINavigationController.Alloc.initWithRootViewController(PreviewController)); // 显示 TiOSHelper.SharedApplication.keyWindow.rootViewController.presentViewController(NavController, True, nil); end; ``` ### 场景2:在WebView中禁用分享下载(适用于UIWebView或WKWebView) 在WebView中,我们可以通过注入JavaScript来禁用长按菜单(上下文菜单)选择文本(防止用户通过选择文本触发分享)。 对于WKWebView(推荐): ```pascal procedure TFormMain.WebView1DidFinishNavigation(ASender: TObject; ANavigation: WKNavigation); var JS: string; begin // 禁用长按菜单 JS := 'document.body.style.webkitTouchCallout="none";'; WebView1.EvaluateJavaScript(StrToNSStr(JS), nil); // 禁用文本选择(可选) JS := 'document.documentElement.style.webkitUserSelect="none";'; WebView1.EvaluateJavaScript(StrToNSStr(JS), nil); end; ``` 另外,我们还可以通过设置`allowsLinkPreview`为False来禁用链接预览(在iOS中,链接预览可能会触发分享): ```pascal WebView1.allowsLinkPreview := False; ``` ### 场景3:自定义视图控制器 如果我们在自定义的视图控制器中,需要确保没有放置可以触发分享或下载的控件(如按钮、链接等),或者对这些控件的事件进行拦截。 例如,在自定义视图中,我们可以覆盖`touchesBegan:withEvent:`方法,但这种方法并不通用。更实际的是,我们只提供我们允许的操作按钮。 ### 注意事项 1. **临时文件管理**:使用临时文件时,确保在适当的时候删除它们(比如在视图控制器消失时)。 2. **生命周期**:根据引用[2][4],我们需要在视图控制器的生命周期方法中(如`viewWillDisappear`)执行清理操作。 例如,在展示QLPreviewController的视图控制器中,我们可以在`viewDidDisappear`中删除临时文件: ```pascal procedure TMyViewController.viewDidDisappear(animated: Boolean); begin inherited; // 删除临时文件 TFile.Delete(FTempFilePath); end; ``` ### 相关问题 1. 如何检测用户截屏行为并防止内容泄露? 2. 在Delphi中如何自定义WKWebView的上下文菜单? 3. 如何实现iOS应用中的文件加密存储? 4. 在iOS中如何完全阻止用户保存图片或文件? 以上方案可以根据具体需求选择使用。需要注意的是,没有绝对的安全,我们的目标是增加非授权分享下载的难度。</think>### Delphi 11 iOS 禁用 UIViewController 分享下载功能 在 Delphi 11 iOS 应用中禁用 UIViewController 的分享下载功能,核心是通过拦截系统操作修改视图行为实现。以下是具体解决方案: #### 方法 1:禁用导航栏分享按钮(适用于 QLPreviewController) ```pascal uses Macapi.ObjectiveC, iOSapi.UIKit, iOSapi.QuickLook; procedure DisableSharing(Controller: QLPreviewController); var NavItem: UINavigationItem; begin if Assigned(Controller) then begin NavItem := Controller.navigationItem; // 移除分享按钮 NavItem.setRightBarButtonItem(nil, False); // 禁用长按菜单 Controller.setEditing(False, False); end; end; // 使用示例 var PreviewController: QLPreviewController; begin PreviewController := TQLPreviewController.Wrap(TQLPreviewController.Alloc.init); PreviewController.setDataSource(...); // 设置数据源 DisableSharing(PreviewController); end; ``` **原理**:直接移除导航栏右侧的分享按钮[^1] #### 方法 2:禁用 WebView 下载(适用于 WKWebView) ```pascal procedure DisableWebDownloads(WebView: WKWebView); var JS: NSString; begin // 禁用长按菜单 JS := StrToNSStr('document.body.style.webkitTouchCallout = "none";'); WebView.evaluateJavaScript(JS, nil); // 禁用文本选择 JS := StrToNSStr('document.documentElement.style.webkitUserSelect = "none";'); WebView.evaluateJavaScript(JS, nil); end; ``` #### 方法 3:使用透明覆盖层拦截操作 ```pascal procedure AddBlockingOverlay(ParentView: UIView); var Overlay: UIView; begin Overlay := TUIView.Wrap(TUIView.Alloc.initWithFrame(ParentView.bounds)); Overlay.setUserInteractionEnabled(True); ParentView.addSubview(Overlay); end; // 在 viewDidLoad 中调用 procedure TMyViewController.viewDidLoad; begin inherited; AddBlockingOverlay(Self.view); end; ``` **原理**:添加透明覆盖层拦截所有触摸事件[^5] #### 方法 4:禁用文档交互控制器 ```pascal type TSecureDocumentDelegate = class(TOCLocal, UIDocumentInteractionControllerDelegate) public function documentInteractionControllerViewControllerForPreview( controller: UIDocumentInteractionController): UIViewController; cdecl; end; function TSecureDocumentDelegate.documentInteractionControllerViewControllerForPreview( controller: UIDocumentInteractionController): UIViewController; begin Result := nil; // 阻止弹出分享菜单 end; // 使用 var DocController: UIDocumentInteractionController; Delegate: TSecureDocumentDelegate; begin DocController := TUIDocumentInteractionController.Wrap( TUIDocumentInteractionController.OCClass.interactionControllerWithURL(NSURL)); Delegate := TSecureDocumentDelegate.Create; DocController.setDelegate(Delegate.GetObjectID); end; ``` #### 完整生命周期集成 ```pascal type TSecureViewController = class(TUIViewController) private FBlockingOverlay: UIView; public procedure viewDidLoad; override; procedure viewWillAppear(animated: Boolean); override; procedure viewDidDisappear(animated: Boolean); override; end; procedure TSecureViewController.viewDidLoad; begin inherited; // 添加拦截层 FBlockingOverlay := TUIView.Wrap(TUIView.Alloc.initWithFrame(Self.view.bounds)); FBlockingOverlay.setUserInteractionEnabled(True); Self.view.addSubview(FBlockingOverlay); end; procedure TSecureViewController.viewWillAppear(animated: Boolean); begin inherited; // 隐藏系统分享按钮 Self.navigationItem.setRightBarButtonItem(nil, animated); end; procedure TSecureViewController.viewDidDisappear(animated: Boolean); begin // 清理资源 FBlockingOverlay.removeFromSuperview; inherited; end; ``` #### 配置 Info.plist 增强限制 ```xml <key>LSSupportsOpeningDocumentsInPlace</key> <false/> <key>UIFileSharingEnabled</key> <false/> ``` #### 注意事项 1. **临时文件策略**:使用临时目录存储文件,退出时自动清除 ```pascal function GetSecureTempPath: String; begin Result := IncludeTrailingPathDelimiter(NSTemporaryDirectory.UTF8String) + 'secure/'; ForceDirectories(Result); end; ``` 2. **iOS 版本适配**:iOS 15+ 需使用 `UIScene` 生命周期管理 3. **备用方案**:使用 `TWebBrowser` 替代原生预览器 ```pascal WebBrowser1.Navigate('javascript: document.body.style.webkitTouchCallout="none";'); ``` --- ### 相关问题 1. 如何检测用户尝试截屏并阻止内容泄露? 2. Delphi 中如何捕获 iOS 视图控制器的生命周期事件? 3. 如何统一 Android iOS 平台的文件安全策略? 4. 临时文件方案在 iOS 后台刷新时如何保证安全性? 5. 如何禁用 WKWebView 中的长按菜单选项? [^1]: 通过修改导航栏属性移除系统分享按钮 [^2]: 视图控制器生命周期中需在 viewWillAppear 阶段应用限制 [^3]: 模态控制器需设置特殊呈现样式 [^4]: 确保在 viewDidLoad 后执行界面修改 [^5]: 透明覆盖层可拦截所有用户交互事件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值