self、nil、id用法专题总结

本文深入探讨Objective-C中的self、nil和id的使用方法,包括属性、getter和setter的原理及作用,以及nil在Objective-C中的特性与应用。详细解释了如何在Objective-C中正确使用这些关键字,帮助开发者更高效地进行Objective-C编程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

self、nil、id用法专题总结

id的用法,举个简单例子,很容易就理解和掌握了。
例:#import <Cocoa/Cocoa.h>
      #import "Converter.h"
     @interface Controller : NSObject {
     IBOutlet  id  converter;

     IBOutlet  id  dollarField;
     IBOutlet  id  rateField;
     IBOutlet  id  totalField;
 }
-(IBAction)convert:(id)sender;

@end
另外再举一个例子。
@interface  MyController:NSObject{
         IBOutlet  id  textField;//插座变量通常使用id关键字动态地指定类型您可将id关键字用作任何对象的类型,意味这个对象的类在运行环境中才确定。 
        // IBOutlet  NSTextField *textField;//当您不需要动态类型对象时,应该静态地指定插座变量为指向对象的指针。
 }

@end



self用法详解,专题总结!
objective-c语言中的self,就相当于C++中的this指针。
学会使用self,首先要搞清楚属性这一概念。
以及理解getter和setter方法,它到底有什么用?
设置器与访问器,提供外界操作 类内部属性的 一个通道。假如,没有这个方法,外界怎么操作类的内部属性。假如不提供这两个方法,那么这个属性的值,就不能为外界所改变。
因为类的属性,一般是private 。属性一般是私有的。


nil用法
Objective-C中nil
nil
nil和C语言的NULL相同,在objc/objc.h中定义。nil表示一个Objctive-C对象,这个对象的指针指向空(没有东西就是空)。
Nil
首字母大写的Nil和nil有一点不一样,Nil定义一个指向空的类(是Class,而不是对象)。
在Objective-C里,nil对象被设计来跟NULL空指针关联的。他们的区别就是nil是一个对象,而NULL只是一个值。而且我们对于nil调用方法,不会产生crash或者抛出异常。
这个技术被framework通过多种不同的方式使用。最主要的就是我们现在在调用方法之前根本无须去检查这个对象是否是nil。假如我们调了nil对象的一个有返回值的方法,那么我们会得到一个nil返回值。
  我们可以通过nil对象让我们的dealloc函数实现看上去更帅一点:
  - (void) dealloc
  {
  self.caption = nil;
  self.photographer = nil;
  [super dealloc];
  }
  之所以可以这么做是因为我们给把nil对象设给了一个成员变量,setter就会retain nil对象(当然了这个时候nil对象啥事情也不会做)然后release旧的对象。这个方式来释放对象其实更好,因为这样做的话,成员变量连指向随机数据的机会都没有,而通过别的方式,出现指向随机数据的情形机会不可避免。注意到我们调用的self.VAR这样的语法,这表示我们正在用setter,而且不会引起任何内存问题。

在Objective-C中,你来对象在功能上等价于其他很多语言中的NULL指针。区别是可以对nil调用方法,而不致导致程序崩溃或抛出异常


我正在编辑【Emmylua】代码,遇到了 【LuaException: Projects/AAAShare/ShiYans/XiaHeMoYaWoGouFengBiShu/Mission/UI/UIXiShouPaiXun/ui_xishoupaixun.lua:609: attempt to index a nil value (field '?')】 ,请帮我检查并改正错误点。我的原始代码如下: 【----@class UIXiShouPaiXun:UIBaseView2 local UIXiShouPaiXun = BaseClass("UIXiShouPaiXun", UIBaseView2) function UIXiShouPaiXun:ShuffleArray(t) if not t then return {} end for i = #t, 2, -1 do local j = math.random(i) t[i], t[j] = t[j], t[i] end return t end -- 创建完调用 function UIXiShouPaiXun:LoadCallBack() -- override --UINameTable init start --上面的七个位置 ---@type UnityEngine.GameObject self.upimg1 = self:FindObj("upimg1") ---@type UnityEngine.GameObject self.upimg2 = self:FindObj("upimg2") ---@type UnityEngine.GameObject self.upimg3 = self:FindObj("upimg3") ---@type UnityEngine.GameObject self.upimg4 = self:FindObj("upimg4") ---@type UnityEngine.GameObject self.upimg5 = self:FindObj("upimg5") ---@type UnityEngine.GameObject self.upimg6 = self:FindObj("upimg6") ---@type UnityEngine.GameObject self.upimg7 = self:FindObj("upimg7") --下面的七个位置 ---@type UnityEngine.GameObject self.downimg1 = self:FindObj("downimg1") ---@type UnityEngine.GameObject self.downimg2 = self:FindObj("downimg2") ---@type UnityEngine.GameObject self.downimg3 = self:FindObj("downimg3") ---@type UnityEngine.GameObject self.downimg4 = self:FindObj("downimg4") ---@type UnityEngine.GameObject self.downimg5 = self:FindObj("downimg5") ---@type UnityEngine.GameObject self.downimg6 = self:FindObj("downimg6") ---@type UnityEngine.GameObject self.downimg7 = self:FindObj("downimg7") --七张图片 ---@type UnityEngine.GameObject self.xiShouImg1 = self:FindObj("xiShouImg1") ---@type UnityEngine.GameObject self.xiShouImg2 = self:FindObj("xiShouImg2") ---@type UnityEngine.GameObject self.xiShouImg3 = self:FindObj("xiShouImg3") ---@type UnityEngine.GameObject self.xiShouImg4 = self:FindObj("xiShouImg4") ---@type UnityEngine.GameObject self.xiShouImg5 = self:FindObj("xiShouImg5") ---@type UnityEngine.GameObject self.xiShouImg6 = self:FindObj("xiShouImg6") ---@type UnityEngine.GameObject self.xiShouImg7 = self:FindObj("xiShouImg7") --提交按钮 ---@type UnityEngine.GameObject self.btn_TiJiao = self:FindObj("btn_TiJiao") --正确答案显示按钮 ---@type UnityEngine.GameObject self.btn_zhengQue = self:FindObj("btn_zhengQue") ---@type UnityEngine.GameObject self.btn_NextStep = self:FindObj("btn_zhengQue") ---@type UnityEngine.Canvas self.CanvasUp = self:FindObj("CanvasUp"):GetComponent(typeof(CS.UnityEngine.Canvas)) ---@type UnityEngine.UI.Image self.CorrectMark = self:FindObj("CorrectMark"):GetComponent(typeof(CS.UnityEngine.UI.Image)) ---@type UnityEngine.UI.Image self.WrongMark = self:FindObj("WrongMark"):GetComponent(typeof(CS.UnityEngine.UI.Image)) ---@type UnityEngine.UI.Image self.righttMark1 = self:FindObj("righttMark1") ---@type UnityEngine.UI.Image self.righttMark2 = self:FindObj("righttMark2") ---@type UnityEngine.UI.Image self.righttMark3 = self:FindObj("righttMark3") ---@type UnityEngine.UI.Image self.righttMark4 = self:FindObj("righttMark4") ---@type UnityEngine.UI.Image self.righttMark5 = self:FindObj("righttMark5") ---@type UnityEngine.UI.Image self.righttMark6 = self:FindObj("righttMark6") ---@type UnityEngine.UI.Image self.righttMark7 = self:FindObj("righttMark7") --UINameTable init end --UIVariableTable init start --UIVariableTable init end --self.testGo1 = MissionManager.Instance:GetGameObject("XXX") ---@type UnityEngine.GameObject[] self.upPositions = { self.upimg1, self.upimg2, self.upimg3, self.upimg4, self.upimg5, self.upimg6, self.upimg7 } ---@type UnityEngine.GameObject[] self.downPositions = { self.downimg1, self.downimg2, self.downimg3, self.downimg4, self.downimg5, self.downimg6, self.downimg7 } ---@type UnityEngine.GameObject[] self.images = { self.xiShouImg1, self.xiShouImg2, self.xiShouImg3, self.xiShouImg4, self.xiShouImg5, self.xiShouImg6, self.xiShouImg7 } ---@type UnityEngine.UI.Image[] self.rightMarks = {} self.rightMarks[1] = self.righttMark1:GetComponent(typeof(CS.UnityEngine.UI.Image)) self.rightMarks[2] = self.righttMark2:GetComponent(typeof(CS.UnityEngine.UI.Image)) self.rightMarks[3] = self.righttMark3:GetComponent(typeof(CS.UnityEngine.UI.Image)) self.rightMarks[4] = self.righttMark4:GetComponent(typeof(CS.UnityEngine.UI.Image)) self.rightMarks[5] = self.righttMark5:GetComponent(typeof(CS.UnityEngine.UI.Image)) self.rightMarks[6] = self.righttMark6:GetComponent(typeof(CS.UnityEngine.UI.Image)) self.rightMarks[7] = self.righttMark7:GetComponent(typeof(CS.UnityEngine.UI.Image)) -- 初始化变量 self.imageStepMap = {} -- 图片到步骤的映射 self.positionToImage = {} -- 位置到图片的映射 self.imageToPosition = {} -- 图片到位置的映射 self.dragStartPositions = {} -- 拖拽起始位置 -- 设置图片的步骤映射 (1-7) for i, img in ipairs(self.images) do self.imageStepMap[img] = i end -- 初始化按钮状态 self:XianYin(self.btn_TiJiao, 0) self:XianYin(self.btn_zhengQue, 0) self:XianYin(self.btn_NextStep, 0) -- 初始化拖拽功能 self:InitDrag() -- 随机排列下方图片 self:ShuffleDownImages() -- 记录画布平面的Z位置 self.canvasZ = self.transform.position.z end -- 销毁前调用 function UIXiShouPaiXun:ReleaseCallBack() -- override --UINameTable delete start self.upimg1 = nil self.upimg2 = nil self.upimg3 = nil self.upimg4 = nil self.upimg5 = nil self.upimg6 = nil self.upimg7 = nil self.downimg1 = nil self.downimg2 = nil self.downimg3 = nil self.downimg4 = nil self.downimg5 = nil self.downimg6 = nil self.downimg7 = nil self.xiShouImg1 = nil self.xiShouImg2 = nil self.xiShouImg3 = nil self.xiShouImg4 = nil self.xiShouImg5 = nil self.xiShouImg6 = nil self.xiShouImg7 = nil self.btn_TiJiao = nil self.btn_zhengQue = nil self.CanvasUp = nil self.CorrectMark = nil self.WrongMark = nil self.righttMark1 = nil self.righttMark2 = nil self.righttMark3 = nil self.righttMark4 = nil self.righttMark5 = nil self.righttMark6 = nil self.righttMark7 = nil --UINameTable delete end --UIVariableTable delete start --UIVariableTable delete end self.upPositions = nil self.downPositions = nil self.images = nil self.rightMarks = nil self.imageStepMap = nil self.positionToImage = nil self.imageToPosition = nil self.dragStartPositions = nil self.canvasZ = nil end -- 打开后调用 function UIXiShouPaiXun:OpenCallBack() -- override --UIEventTable init start --点提交按钮的事件 self.onClickTiJiaoEvent = BindTool.Bind(self, self.OnClickTiJiao) self:ListenEvent("TiJiao", self.onClickTiJiaoEvent) --点正确答案按钮的事件 self.onClickZhengQueDaAnEvent = BindTool.Bind(self, self.OnClickZhengQueDaAn) self:ListenEvent("ZhengQueDaAn", self.onClickZhengQueDaAnEvent) --UIEventTable init end for _, masks in ipairs(self.rightMarks) do self:XianYin(masks.gameObject, 0) end end -- 关闭前调用 function UIXiShouPaiXun:CloseCallBack() -- override --UIEventTable delete start self:ClearEvent("TiJiao") self.onClickTiJiaoEvent = nil self:ClearEvent("ZhengQueDaAn") self.onClickZhengQueDaAnEvent = nil MissionManager.Instance:UnbindCustomBtn("下一步") --UIEventTable delete end end -- 刷新,使用Flush来触发此方法。 function UIXiShouPaiXun:OnFlush(param_list) -- override end -- 集中注册信息 function UIXiShouPaiXun:OnAddListener() -- override end -- 集中注销消息 function UIXiShouPaiXun:OnRemoveListener() -- override end ---@param key string ---@param saver Nirvana.MissionSystem.Save.Saver function UIXiShouPaiXun:Back(key, saver) --回退 --saver:ResetGameObject(key, self.testGo1) end ---@param key string ---@param saver Nirvana.MissionSystem.Save.Saver function UIXiShouPaiXun:Save(key, saver, ...) --保存 --saver:SaveGameObject(key, self.testGo1) end function UIXiShouPaiXun:SetParam(...) -- override -- 执行此方法的时候,请保证UI已经打开。 end function UIXiShouPaiXun:Start(...) -- override -- 开始 -- eg 1:直接完成 -- FWGlobalEventSystem:Fire(ShiYanEventType.OPERATE_OVER, self:GetName()) -- eg 2:申请交互 -- FWGlobalEventSystem:Fire(ShiYanEventType.TRY_INTERACT, self:GetName()) end function UIXiShouPaiXun:Imm(...) -- override -- 直接完成 FWGlobalEventSystem:Fire(ShiYanEventType.OPERATE_OVER, self:GetName()) end function UIXiShouPaiXun:Stop() -- override -- 停止 end function UIXiShouPaiXun:Continue() -- override -- eg 2:交互回调 --FWGlobalEventSystem:Fire(ShiYanEventType.OPERATE_OVER, self:GetName(),0,1) return false end function UIXiShouPaiXun:Guide(...) -- override return false end function UIXiShouPaiXun:XianYin(object, a) if object then -- 添加空值检查 object:SetActive(a == 1) end end function UIXiShouPaiXun:InitDrag() for _, img in ipairs(self.images) do self:AddDragEvents(img) end end function UIXiShouPaiXun:AddDragEvents(img) -- 获取或添加EventTrigger组件 local eventTrigger = img:GetComponent(typeof(CS.UnityEngine.EventSystems.EventTrigger)) if eventTrigger == nil then eventTrigger = img:AddComponent(typeof(CS.UnityEngine.EventSystems.EventTrigger)) else -- 清除现有事件 eventTrigger.triggers:Clear() end -- 开始拖拽事件 local beginEntry = CS.UnityEngine.EventSystems.EventTrigger.Entry() beginEntry.eventID = CS.UnityEngine.EventSystems.EventTriggerType.BeginDrag beginEntry.callback:AddListener(function() self:OnBeginDrag(img) end) eventTrigger.triggers:Add(beginEntry) -- 拖拽中事件 local dragEntry = CS.UnityEngine.EventSystems.EventTrigger.Entry() dragEntry.eventID = CS.UnityEngine.EventSystems.EventTriggerType.Drag dragEntry.callback:AddListener(function() self:OnDrag(img) end) eventTrigger.triggers:Add(dragEntry) -- 结束拖拽事件 local endEntry = CS.UnityEngine.EventSystems.EventTrigger.Entry() endEntry.eventID = CS.UnityEngine.EventSystems.EventTriggerType.EndDrag endEntry.callback:AddListener(function() self:OnEndDrag(img) end) eventTrigger.triggers:Add(endEntry) end -- 随机排列下方图片 function UIXiShouPaiXun:ShuffleDownImages() -- 重置所有映射关系 self.positionToImage = {} self.imageToPosition = {} local t = { 1, 2, 3, 4, 5, 6, 7 } -- 创建1-7的随机顺序 local randomOrder = self:ShuffleArray(t) -- 将图片放置到随机位置 for i, img in ipairs(self.images) do local pos = self.downPositions[randomOrder[i]] img.transform:SetParent(pos.transform) img.transform.localPosition = Vector3.zero img.transform:SetAsLastSibling() -- 确保显示在最上层 end end -- 开始拖拽处理 function UIXiShouPaiXun:OnBeginDrag(img) -- 记录当前拖拽的图片 self.draggingImage = img -- 记录拖拽前的父对象位置 self.dragStartParent = img.transform.parent self.dragStartPosition = img.transform.localPosition -- 记录原始Z坐标 self.originalZ = img.transform.position.z -- 提升层级到最上层 img.transform:SetParent(self.transform) img.transform:SetAsLastSibling() end -- 拖拽中处理(跟随鼠标移动) function UIXiShouPaiXun:OnDrag(img) -- 获取鼠标位置 local mousePos = CS.UnityEngine.Input.mousePosition -- 获取画布相机 local camera = self.CanvasUp.worldCamera if camera == nil then camera = CS.UnityEngine.Camera.main end -- 确保有相机 if camera then -- 计算从相机到画布平面的距离 local distance = math.abs(camera.transform.position.z - self.canvasZ) -- 将屏幕坐标转换为世界坐标 local worldPos = camera:ScreenToWorldPoint( CS.UnityEngine.Vector3(mousePos.x, mousePos.y, distance) ) -- 保持原始Z坐标 worldPos = CS.UnityEngine.Vector3(worldPos.x, worldPos.y, self.originalZ) img.transform.position = worldPos end end -- 结束拖拽处理 function UIXiShouPaiXun:OnEndDrag(img) local hitPosition = nil -- 检查上方位置 for _, pos in ipairs(self.upPositions) do if self:IsPointInRect(pos, img) then hitPosition = pos break end end -- 检查下方位置 if not hitPosition then for _, pos in ipairs(self.downPositions) do if self:IsPointInRect(pos, img) then hitPosition = pos break end end end if hitPosition then self:PlaceImageToPosition(img, hitPosition) else -- 返回原始位置 img.transform:SetParent(self.dragStartParent) img.transform.localPosition = self.dragStartPosition end self.draggingImage = nil self:CheckAllPlaced() end function UIXiShouPaiXun:IsPointInRect(position, img) if not position or not img then return false end local rectTransform = position:GetComponent(typeof(CS.UnityEngine.RectTransform)) if not rectTransform then return false end local camera = self.CanvasUp.worldCamera if not camera then camera = CS.UnityEngine.Camera.main if not camera then -- 如果还是没有相机,返回false return false end end local imgRectTransform = img:GetComponent(typeof(CS.UnityEngine.RectTransform)) if not imgRectTransform then return false end local imgCenter = imgRectTransform.position local screenPos = camera:WorldToScreenPoint(imgCenter) -- 确保坐标有效(避免相机在物体后方的情况) if screenPos.z <= 0 then return false end local screenPos2 = CS.UnityEngine.Vector2(screenPos.x, screenPos.y) return CS.UnityEngine.RectTransformUtility.RectangleContainsScreenPoint( rectTransform, screenPos2, camera ) end -- 将图片放置到指定位置 function UIXiShouPaiXun:PlaceImageToPosition(img, position) -- 如果位置已有图片,交换位置 if self.positionToImage[position] then local otherImg = self.positionToImage[position] self:SwapImages(img, otherImg) else -- 更新映射关系 if self.imageToPosition[img] then self.positionToImage[self.imageToPosition[img]] = nil end self.positionToImage[position] = img self.imageToPosition[img] = position -- 设置新父对象位置 img.transform:SetParent(position.transform) img.transform.localPosition = Vector3.zero -- 关键:设置为第一个子节点(底层) img.transform:SetAsFirstSibling() end end -- 交换两个图片的位置 function UIXiShouPaiXun:SwapImages(img1, img2) local pos1 = self.imageToPosition[img1] local pos2 = self.imageToPosition[img2] -- 更新映射关系 self.positionToImage[pos1] = img2 self.positionToImage[pos2] = img1 self.imageToPosition[img1] = pos2 self.imageToPosition[img2] = pos1 -- 交换父对象 img1.transform:SetParent(pos2.transform) img1.transform.localPosition = Vector3.zero img1.transform:SetAsFirstSibling() img2.transform:SetParent(pos1.transform) img2.transform.localPosition = Vector3.zero img2.transform:SetAsFirstSibling() end -- 检查所有上方位置是否都已放置图片 function UIXiShouPaiXun:CheckAllPlaced() local allPlaced = true -- 遍历所有上方位置 for _, pos in ipairs(self.upPositions) do if not self.positionToImage[pos] then allPlaced = false break end end -- 根据检查结果更新提交按钮状态 if self.btn_TiJiao then self.btn_TiJiao:SetActive(allPlaced) end end -- 点击提交按钮事件处理 function UIXiShouPaiXun:OnClickTiJiao() -- 禁用所有拖拽功能 for _, img in ipairs(self.images) do if img then local eventTrigger = img:GetComponent(typeof(CS.UnityEngine.EventSystems.EventTrigger)) if eventTrigger then eventTrigger.enabled = false end end end -- 检查每个位置是否正确 local allCorrect = true for i, pos in ipairs(self.upPositions) do local img = self.positionToImage[pos] if img and self.imageStepMap[img] == i then -- 位置正确,显示正确标记 self:ShowMark(img, true, i) else -- 位置错误,显示错误标记 self:ShowMark(img, false) allCorrect = false end end -- 更新按钮状态 if self.btn_TiJiao then self.btn_TiJiao:SetActive(false) end if allCorrect then -- 全部正确,显示下一步按钮 self:ShowNextStepButton() else -- 存在错误,显示正确答案按钮 if self.btn_zhengQue then self.btn_zhengQue:SetActive(true) end end end -- 在图片上显示正确/错误标记 function UIXiShouPaiXun:ShowMark(img, isCorrect, num) if not img then return end -- 根据正确性选择标记名称 self.rightMarks[num].sprite = isCorrect and self.CorrectMark.sprite or self.WrongMark.sprite if mark then self:XianYin(self.rightMarks[num].gameObject, 1) end end -- 隐藏所有图片上的标记 function UIXiShouPaiXun:HideAllMarks() for i, img in ipairs(self.images) do if img then ---- 查找正确标记 --local correctMark = img.transform:Find("CorrectMark") --if correctMark then -- correctMark.gameObject:SetActive(false) --end -- ---- 查找错误标记 --local wrongMark = img.transform:Find("WrongMark") --if wrongMark then -- wrongMark.gameObject:SetActive(false) --end self:XianYin(self.rightMarks[i].gameObject, 0) end end end -- 点击"正确答案"按钮事件处理 function UIXiShouPaiXun:OnClickZhengQueDaAn() -- 隐藏下方位置 for _, pos in ipairs(self.downPositions) do if pos then pos:SetActive(false) end end -- 隐藏所有标记 self:HideAllMarks() -- 将图片移动到正确位置 for i, pos in ipairs(self.upPositions) do if pos then local correctImg = self.images[i] -- 获取对应步骤的图片 if correctImg then correctImg.transform:SetParent(pos.transform) correctImg.transform.localPosition = Vector3.zero end end end ---- 更新按钮状态 --if self.btn_zhengQue then -- self.btn_zhengQue:SetActive(false) --end self:XianYin(self.btn_zhengQue.gameObject, 0) -- 显示下一步按钮 self:ShowNextStepButton() end function UIXiShouPaiXun:ShowNextStepButton() if self.btn_NextStep then self.btn_NextStep:SetActive(true) end MissionManager.Instance:BindCustomBtn("下一步", function() self:OnClickNextStep() end) end -- 点击下一步按钮事件处理 function UIXiShouPaiXun:OnClickNextStep() -- 触发完成事件 FWGlobalEventSystem:Fire(ShiYanEventType.OPERATE_OVER, self:GetName()) end return UIXiShouPaiXun;】
06-04
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator { [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; // 保存当前播放状态进度 NSIndexPath * indexPath = [self metalienKK_getCurrentIndexPath]; MetalienKKDynamicModel * currentModel = self.dataSource[indexPath.row]; CGFloat playTime = currentModel.currentPlaybackTime; BOOL wasPlaying = currentModel.isPlaying; BOOL willBeFullScreen = (size.width > size.height); currentModel.isFullScreen = willBeFullScreen; // 更新控件可见性 self.metalienKK_statusImageView.hidden = willBeFullScreen; self.backButton.hidden = willBeFullScreen; self.searchButton.hidden = willBeFullScreen; self.shareButton.hidden = willBeFullScreen; self.portraitButton.hidden = !willBeFullScreen; // 暂停当前播放但不重置 MetalienKKShortVideoItemCollectionViewCell * currentCell = [self.collectionView cellForItemAtIndexPath:indexPath]; [currentCell.metalienKK_playerView metalienKK_pauseShortVideoWithoutReset]; // 更新约束 [self metalienKK_updateContainerFrameForSize:size isFullScreen:willBeFullScreen atIndexPath:indexPath]; // 更新布局而不重载数据 [coordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context) { // 立即刷新布局避免闪烁 [UIView performWithoutAnimation:^{ [self.collectionView.collectionViewLayout invalidateLayout]; [self.collectionView layoutIfNeeded]; [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO]; }]; // 更新旋转状态 [currentCell.metalienKK_playerView metalienKK_shortVideoPlayerViewRotate:willBeFullScreen]; } completion:^(id <UIViewControllerTransitionCoordinatorContext> context) { self.collectionView.scrollEnabled = !currentModel.isFullScreen; // 恢复播放状态 if (wasPlaying) { [currentCell.metalienKK_playerView metalienKK_startPlayShortVideo]; [currentCell.metalienKK_playerView metalienKK_seekToTime:playTime]; } else { [currentCell.metalienKK_playerView metalienKK_pauseShortVideo]; } // 执行等待中的动作 if (self.interfaceOrientationBlock) { self.interfaceOrientationBlock(); self.interfaceOrientationBlock = nil; } }]; } #import "MetalienKKShortVideoPlayerView.h" #define MetalienKK_kMiniPlayerViewHeight (265.0) #define MetalienKK_kBottomViewHeight (55.0 + MetalienKK_SafeAreaBottom) #define MetalienKK_kProgressViewSize CGSizeMake(MetalienKK_ScreenWidth - 15*2.0, 18.0) #define MetalienKK_kFullScreenDefaultSize CGSizeMake(110, 30) #define MetalienKK_FullScreenOpenButtonFont MetalienKK_RegularFont(12) @implementation MetalienKKShortVideoPlayerView /// 加载子视图 - (void)metalienKK_loadSubviews { // 新增视图 [self addSubview:self.metalienKK_playerView]; [self addSubview:self.metalienKK_maskView]; [self addSubview:self.metalienKK_bottomView]; [self addSubview:self.metalienKK_durationLabel]; [self addSubview:self.metalienKK_contentView]; [self addSubview:self.metalienKK_userView]; [self addSubview:self.metalienKK_progressView]; [self addSubview:self.metalienKK_popupView]; [self addSubview:self.metalienKK_loadingView]; [self addSubview:self.metalienKK_fullScreenButton]; [self addSubview:self.metalienKK_playImageView]; [self.metalienKK_playerView addSubview:self.metalienKK_videoEngine.playerView]; [self.metalienKK_bottomView addSubview:self.metalienKK_likeButton]; [self.metalienKK_bottomView addSubview:self.metalienKK_bottomLineView_1]; [self.metalienKK_bottomView addSubview:self.metalienKK_bottomLineView_2]; [self.metalienKK_bottomView addSubview:self.metalienKK_commentButton]; [self.metalienKK_bottomView addSubview:self.metalienKK_wowButton]; // 设置约束 [self.metalienKK_bottomView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.bottom.right.equalTo(self); make.height.mas_offset(MetalienKK_kBottomViewHeight); }]; [self.metalienKK_likeButton mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(self.metalienKK_bottomView); make.top.equalTo(self.metalienKK_bottomView); make.width.equalTo(self.metalienKK_bottomView.mas_width).multipliedBy(1/3.0); make.height.mas_offset(55.0); }]; [self.metalienKK_bottomLineView_1 mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.equalTo(self.metalienKK_likeButton); make.right.equalTo(self.metalienKK_likeButton.mas_left); make.size.mas_offset(CGSizeMake(1, 20)); }]; [self.metalienKK_bottomLineView_2 mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.equalTo(self.metalienKK_likeButton); make.left.equalTo(self.metalienKK_likeButton.mas_right); make.size.mas_equalTo(self.metalienKK_bottomLineView_1); }]; [self.metalienKK_commentButton mas_makeConstraints:^(MASConstraintMaker *make) { make.top.left.equalTo(self.metalienKK_bottomView); make.right.equalTo(self.metalienKK_bottomLineView_1.mas_left); make.height.mas_equalTo(self.metalienKK_likeButton); }]; [self.metalienKK_wowButton mas_makeConstraints:^(MASConstraintMaker *make) { make.top.right.equalTo(self.metalienKK_bottomView); make.left.equalTo(self.metalienKK_bottomLineView_2.mas_right); make.height.mas_equalTo(self.metalienKK_likeButton); }]; [self.metalienKK_playerView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.left.right.equalTo(self); make.bottom.equalTo(self).offset(-MetalienKK_kBottomViewHeight); }]; [self.metalienKK_videoEngine.playerView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self.metalienKK_playerView); }]; [self.metalienKK_popupView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.mas_bottom); // 初始位置在底部之外 make.left.right.equalTo(self); make.height.mas_equalTo(MetalienKK_ScreenHeight - MetalienKK_StatusBarHeight - MetalienKK_kMiniPlayerViewHeight); }]; [self.metalienKK_maskView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self.metalienKK_playerView); }]; [self.metalienKK_playImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.center.equalTo(self.metalienKK_playerView); make.size.mas_offset(CGSizeMake(40, 40)); }]; [self.metalienKK_progressView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(self); make.bottom.equalTo(self.metalienKK_bottomView.mas_top); make.size.mas_offset(MetalienKK_kProgressViewSize); }]; [self.metalienKK_durationLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.equalTo(self.metalienKK_progressView); make.bottom.equalTo(self.metalienKK_progressView.mas_top); }]; [self.metalienKK_contentView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.equalTo(self); make.bottom.equalTo(self.metalienKK_progressView.mas_top); make.height.mas_offset(20.0); }]; [self.metalienKK_userView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.equalTo(self); make.bottom.equalTo(self.metalienKK_contentView.mas_top).offset(-12); make.height.mas_offset(36.0); }]; [self.metalienKK_fullScreenButton mas_makeConstraints:^(MASConstraintMaker *make) { make.center.equalTo(self.metalienKK_playerView); make.size.mas_equalTo(MetalienKK_kFullScreenDefaultSize); }]; [self.metalienKK_loadingView mas_makeConstraints:^(MASConstraintMaker *make) { make.center.equalTo(self.metalienKK_playerView); make.left.right.equalTo(self).inset(15.0); make.height.mas_equalTo(60); }]; // 按钮布局 [self.metalienKK_commentButton metalienKK_layoutWithStyle:MetalienKKButtonEdgeInsetsStyleImageLeft andSpace:4.0]; [self.metalienKK_likeButton metalienKK_layoutWithStyle:MetalienKKButtonEdgeInsetsStyleImageLeft andSpace:4.0]; [self.metalienKK_wowButton metalienKK_layoutWithStyle:MetalienKKButtonEdgeInsetsStyleImageLeft andSpace:4.0]; // 创建通知 [self metalienKK_setupNotifications]; } /// 创建通知 - (void)metalienKK_setupNotifications { // 应用进入后台时暂停 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActiveNotification) name:UIApplicationWillResignActiveNotification object:nil]; // 应用返回前台时恢复 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidBecomeActiveNotification) name:UIApplicationDidBecomeActiveNotification object:nil]; } /// 显示&隐藏控件 - (void)metalienKK_showAndHiddenControls { self.metalienKK_fullScreenButton.hidden = self.responseData.isFullScreen ?:(!self.responseData.isHorizontalResolution); self.metalienKK_bottomView.hidden = self.responseData.isFullScreen; self.metalienKK_contentView.hidden = self.responseData.isFullScreen ?:!((self.responseData.isShowTitle || self.responseData.isShowContent)); self.metalienKK_durationLabel.hidden = self.responseData.isFullScreen ? self.responseData.isPlaying:YES; self.metalienKK_userView.hidden = self.responseData.isFullScreen ? self.responseData.isPlaying:NO; self.metalienKK_progressView.hidden = self.responseData.isFullScreen ? self.responseData.isPlaying:NO; } /// 重置约束 - (void)metalienKK_remarkConstraints { // 视频播放器 [self.metalienKK_playerView mas_remakeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self); make.left.right.equalTo(self).inset(self.responseData.isFullScreen ? 76.0:0.0); make.bottom.equalTo(self).offset(self.responseData.isFullScreen ? 0.0:(-MetalienKK_kBottomViewHeight)); }]; [self.metalienKK_videoEngine.playerView mas_remakeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self.metalienKK_playerView); }]; // 用户信息视图 [self.metalienKK_userView mas_remakeConstraints:^(MASConstraintMaker *make) { if (self.responseData.isFullScreen) { make.top.equalTo(self).offset(20); make.left.right.equalTo(self).inset(94); } else { make.left.right.equalTo(self); if (!self.metalienKK_contentView.hidden) { make.bottom.equalTo(self.metalienKK_contentView.mas_top).offset(-12); } else { make.bottom.equalTo(self.metalienKK_progressView.mas_top); } } make.height.mas_offset(36.0); }]; // 进度条 [self.metalienKK_progressView mas_remakeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(self); if (self.responseData.isFullScreen) { make.left.right.equalTo(self).inset(50); make.bottom.equalTo(self).offset(-39); make.height.mas_equalTo(MetalienKK_kProgressViewSize.height); } else { make.bottom.equalTo(self.metalienKK_bottomView.mas_top); make.size.mas_offset(MetalienKK_kProgressViewSize); } }]; // 内容 if (!self.metalienKK_contentView.hidden) { [self.metalienKK_contentView mas_remakeConstraints:^(MASConstraintMaker *make) { CGFloat contentHeight = [self.metalienKK_contentView metalienKK_getContentViewHeight]; make.left.right.equalTo(self); make.bottom.equalTo(self.metalienKK_progressView.mas_top); make.height.mas_offset(contentHeight); }]; } // 全屏播放按钮 if (!self.metalienKK_fullScreenButton.hidden) { [self.metalienKK_fullScreenButton mas_remakeConstraints:^(MASConstraintMaker *make) { CGSize tempSize = [self.metalienKK_fullScreenButton.currentTitle metalienKK_calculateMultipleLinesTextSizeWithFont:MetalienKK_FullScreenOpenButtonFont andMaxSize:CGSizeMake(CGFLOAT_MAX, MetalienKK_kFullScreenDefaultSize.height)]; CGFloat tempWidth = 2*12.0 + 20.0 + 8.0 + tempSize.width; if (tempWidth < MetalienKK_kFullScreenDefaultSize.width) { tempWidth = MetalienKK_kFullScreenDefaultSize.width; } CGFloat tempVideoHeight = (self.responseData.videoHeight/(self.responseData.videoWidth*1.0)) * MetalienKK_ScreenWidth; make.centerX.equalTo(self.metalienKK_playerView); make.centerY.equalTo(self.metalienKK_playerView).offset(tempVideoHeight/2.0 + 12.0 + MetalienKK_kFullScreenDefaultSize.height/2.0); make.size.mas_equalTo(CGSizeMake(tempWidth, MetalienKK_kFullScreenDefaultSize.height)); }]; } // 立即刷新 [self layoutIfNeeded]; } # pragma mark - Public /// 视图复用 - (void)metalienKK_shortVideoPlayerViewReuse { // 暂停播放 [self metalienKK_pauseShortVideo]; // 重置播放器 [self.metalienKK_videoEngine resetPlayerVideoProcessor]; } /// 视图旋转 - (void)metalienKK_shortVideoPlayerViewRotate:(BOOL)isFullScreen { // 标记 self.responseData.isFullScreen = isFullScreen; // 显示&隐藏控件 [self metalienKK_showAndHiddenControls]; // 重置约束 [self metalienKK_remarkConstraints]; } /// 刷新数据 - (void)metalienKK_reloadVideoPlayerViewWithData:(MetalienKKDynamicModel *)responseData { // 缓存 self.responseData = responseData; // 显示&隐藏控件 self.metalienKK_loadingView.hidden = responseData.isReadyDisplay; self.metalienKK_playImageView.hidden = YES; self.metalienKK_fullScreenButton.hidden = !responseData.isHorizontalResolution; // 刷新数据 [self.metalienKK_userView metalienKK_refreshShortVideoUser:responseData]; [self.metalienKK_popupView metalienKK_reloadShortVideoViewData:responseData]; // 判断是否显示文本 BOOL isShowContent = (responseData.isShowTitle || responseData.isShowContent); self.metalienKK_contentView.hidden = !isShowContent; if (isShowContent) { [self.metalienKK_contentView metalienKK_refreshShortVideoContent:responseData]; } // 视频进度 [self.metalienKK_progressView metalienKK_shortVideoSetProgress:responseData.currentPlaybackProgress]; // 设置视频资源 [self metalienKK_setupVideoEngineSource]; // 显示&隐藏控件 [self metalienKK_showAndHiddenControls]; // 重置约束 [self metalienKK_remarkConstraints]; } /// 设置视频资源 - (void)metalienKK_setupVideoEngineSource { if (self.responseData.metalienKK_mediaID.length > 0 && self.responseData.metalienKK_mediaToken.length > 0) { // 设置视频资源(使用 Video ID) [self metalienKK_setShortVideoSourceWithVideoID:self.responseData.metalienKK_mediaID andToken:self.responseData.metalienKK_mediaToken]; } else if (self.responseData.metalienKK_href.length > 0) { // 设置视频资源(使用 HTTP URL) [self metalienKK_setShortVideoSourceWithURL:self.responseData.metalienKK_href]; } } /// 预加载方法 - (void)metalienKK_preloadVideo { // 设置视频资源 [self metalienKK_setupVideoEngineSource]; // 准备播放 [self.metalienKK_videoEngine prepareToPlay]; } /// 开始播放 - (void)metalienKK_startPlayShortVideo { // 标识 self.responseData.isPlaying = YES; // 隐藏播放按钮 self.metalienKK_playImageView.hidden = YES; // 弱引用 MetalienKK_WeakSelf(self); // 播放进度 [self.metalienKK_videoEngine addPeriodicTimeObserverForInterval:0.5 queue:dispatch_get_main_queue() usingBlock:^{ // 强引用 MetalienKK_StrongSelf(self); // 刷新视频播放进度 [self metalienKK_refreshShortVideoPlaybackProgress]; }]; // 指定视频播放开始时间 [self.metalienKK_videoEngine setOptionForKey:VEKKeyPlayerStartTime_CGFloat value:@(self.responseData.currentPlaybackTime)]; // 播放视频 [self.metalienKK_videoEngine play]; } /// 暂停播放 - (void)metalienKK_pauseShortVideo { // 显示播放按钮 self.metalienKK_playImageView.hidden = NO; // 手动标记 self.responseData.isPlaying = NO; // 暂停播放 [self.metalienKK_videoEngine pause:YES]; // 移除监听 [self.metalienKK_videoEngine removeTimeObserver]; } /// 重置播放器 - (void)metalienKK_resetPlayer { // 显示播放按钮 self.metalienKK_playImageView.hidden = NO; // 手动标记 self.responseData.isPlaying = NO; // // 视频进度 // [self.metalienKK_progressView metalienKK_shortVideoSetProgress:0]; // 移除监听 [self.metalienKK_videoEngine removeTimeObserver]; // 重置播放器 [self.metalienKK_videoEngine pause:YES]; // [self.metalienKK_videoEngine resetPlayerVideoProcessor]; } // 无重置的暂停 - (void)metalienKK_pauseShortVideoWithoutReset { [self.metalienKK_videoEngine pause]; self.responseData.isPlaying = NO; } // 跳转到指定时间 - (void)metalienKK_seekToTime:(CGFloat)time { MetalienKK_WeakSelf(self); [self.metalienKK_videoEngine setCurrentPlaybackTime:time complete:^(BOOL success) { if (success) { [weak_self metalienKK_refreshShortVideoPlaybackProgress]; } }]; } # pragma mark - Notification Event /// App 进入后台 - (void)appWillResignActiveNotification { if (self.responseData.isPlaying) { [self metalienKK_pauseShortVideo]; } } /// App 即将进入前台 - (void)appDidBecomeActiveNotification { if (self.responseData.isPlaying) { [self metalienKK_startPlayShortVideo]; } } # pragma mark - Event /// 设置视频资源(使用 Video ID) - (void)metalienKK_setShortVideoSourceWithVideoID:(NSString *)videoID andToken:(NSString *)token { TTVideoEngineVidSource * videoSource = [[TTVideoEngineVidSource alloc] initWithVid:videoID playAuthToken:token resolution:TTVideoEngineResolutionTypeAuto]; [self.metalienKK_videoEngine setVideoEngineVideoSource:videoSource]; } /// 设置视频资源(使用 HTTP URL) - (void)metalienKK_setShortVideoSourceWithURL:(NSString *)videoURL { NSString * cacheKey = [videoURL metalienKK_coverToMD5String]; TTVideoEngineUrlSource * urlSource = [[TTVideoEngineUrlSource alloc] initWithUrl:videoURL cacheKey:cacheKey]; [self.metalienKK_videoEngine setVideoEngineVideoSource:urlSource]; } /// 刷新视频播放进度 - (void)metalienKK_refreshShortVideoPlaybackProgress { CGFloat playTime = self.metalienKK_videoEngine.currentPlaybackTime; CGFloat duration = self.metalienKK_videoEngine.duration; CGFloat progress = (playTime/duration); [self.metalienKK_progressView metalienKK_shortVideoSetProgress:progress]; // 视频播放进度 NSString * playTimeString = [NSString metalienKK_secondConvertToFormat:playTime]; NSString * durationString = [NSString metalienKK_secondConvertToFormat:duration]; NSString * content = [NSString stringWithFormat:@"%@ / %@", playTimeString, durationString]; NSRange playTimeRange = [content rangeOfString:playTimeString]; NSMutableAttributedString * attributedString = [[NSMutableAttributedString alloc] initWithString:content]; [attributedString addAttribute:NSForegroundColorAttributeName value:MetalienKK_ColorHexAlpha(@"#A8B0B9", 0.7) range:attributedString.yy_rangeOfAll]; [attributedString addAttribute:NSForegroundColorAttributeName value:UIColor.whiteColor range:playTimeRange]; self.metalienKK_durationLabel.attributedText = attributedString; // 缓存 self.responseData.currentPlaybackProgress = progress; self.responseData.currentPlaybackTime = playTime; } /// 弹出视图 - (void)metalienKK_showPopupViewAtIndex:(int)index { // 选中处理 if (index >= 0) { [self.metalienKK_popupView metalienKK_popupViewDidSelectIndex:index]; } // 控件显示&隐藏 self.metalienKK_fullScreenButton.hidden = YES; self.metalienKK_popupView.hidden = NO; // 标记是否弹出视图 [self metalienKK_markPopupViewIsShow:YES]; // 重置约束 [self.metalienKK_playerView mas_remakeConstraints:^(MASConstraintMaker *make) { make.top.left.right.equalTo(self); make.height.mas_equalTo(MetalienKK_kMiniPlayerViewHeight); }]; [self.metalienKK_popupView mas_remakeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.metalienKK_playerView.mas_bottom); make.left.right.equalTo(self); make.height.mas_equalTo(MetalienKK_ScreenHeight - MetalienKK_StatusBarHeight - MetalienKK_kMiniPlayerViewHeight); }]; // 创建弹簧动画器 UISpringTimingParameters * springParams = [[UISpringTimingParameters alloc] initWithDampingRatio:0.75 initialVelocity:CGVectorMake(0.5, 0.5)]; UIViewPropertyAnimator * animator = [[UIViewPropertyAnimator alloc] initWithDuration:0.4 timingParameters:springParams]; [animator addAnimations:^{ // 强制立即刷新布局 [self layoutIfNeeded]; }]; [animator addCompletion:^(UIViewAnimatingPosition finalPosition) { [self metalienKK_disableCollectionViewScroll:YES]; }]; [animator startAnimation]; } /// 关闭视图 - (void)metalienKK_closePopupView { // 标记是否弹出视图 [self metalienKK_markPopupViewIsShow:NO]; // 重置约束 [self.metalienKK_playerView mas_remakeConstraints:^(MASConstraintMaker *make) { make.top.left.right.equalTo(self); make.bottom.equalTo(self).offset(-MetalienKK_kBottomViewHeight); }]; [self.metalienKK_popupView mas_remakeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.mas_bottom); make.left.right.equalTo(self); make.height.mas_equalTo(MetalienKK_ScreenHeight - MetalienKK_StatusBarHeight - MetalienKK_kMiniPlayerViewHeight); }]; // 使用相同的动画参数 UISpringTimingParameters * springParams = [[UISpringTimingParameters alloc] initWithDampingRatio:0.8 initialVelocity:CGVectorMake(0, 0)]; UIViewPropertyAnimator * animator = [[UIViewPropertyAnimator alloc] initWithDuration:0.35 timingParameters:springParams]; [animator addAnimations:^{ // 强制立即刷新布局 [self layoutIfNeeded]; }]; [animator addCompletion:^(UIViewAnimatingPosition finalPosition) { self.metalienKK_fullScreenButton.hidden = !self.responseData.isHorizontalResolution; self.metalienKK_popupView.hidden = YES; [self metalienKK_disableCollectionViewScroll:NO]; }]; [animator startAnimation]; } /// 标记是否弹出视图 - (void)metalienKK_markPopupViewIsShow:(BOOL)isShow { // 缓存 self.responseData.isShowingPopupView = isShow; // 弹出&收起视图 if ([self.delegate respondsToSelector:@selector(metalienKK_collectionViewCellDidShowPopupView:)]) { [self.delegate metalienKK_collectionViewCellDidShowPopupView:isShow]; } } /// 禁止滚动 - (void)metalienKK_disableCollectionViewScroll:(BOOL)isDisable { // 移除手势 [self.metalienKK_maskView removeGestureRecognizer:self.metalienKK_maskViewBlockPanGesture]; // 判断是否禁止 if (isDisable) { [self.metalienKK_maskView addGestureRecognizer:self.metalienKK_maskViewBlockPanGesture]; } } # pragma mark - Gesture /// 点击视频 - (void)metalienKK_maskViewTapGesture:(UITapGestureRecognizer *)gesture { // 判断是否正在弹出视图 if (self.responseData.isShowingPopupView) { [self metalienKK_closePopupView]; } else { if (self.metalienKK_videoEngine.playbackState == TTVideoEnginePlaybackStatePlaying) { [self metalienKK_pauseShortVideo]; } else { [self metalienKK_startPlayShortVideo]; } } } /// 点击内容视图 - (void)metalienKK_contentViewTapGesture:(UITapGestureRecognizer *)gesture { [self metalienKK_showPopupViewAtIndex:0]; } /// 视频窗口滑动手势 - (void)metalienKK_maskViewPanGesture:(UIPanGestureRecognizer *)gesture { // Nothing ... } /// 预览视图滑动手势 - (void)metalienKK_popupViewPanGesture:(UIPanGestureRecognizer *)gesture { static CGPoint originalCenter; static CGRect originalFrame; static CGFloat initialTranslationY; CGPoint translation = [gesture translationInView:self]; CGPoint velocity = [gesture velocityInView:self]; switch (gesture.state) { case UIGestureRecognizerStateBegan: // 记录初始位置 originalCenter = self.metalienKK_popupView.center; originalFrame = self.metalienKK_popupView.frame; initialTranslationY = translation.y; break; case UIGestureRecognizerStateChanged: { // 计算新的Y位置(限制在屏幕范围内) CGFloat newY = originalFrame.origin.y + translation.y; CGFloat minY = MetalienKK_kMiniPlayerViewHeight; CGFloat maxY = MetalienKK_ScreenHeight; // 限制拖拽范围(不能超过屏幕顶部/底部) newY = MIN(MAX(newY, minY), maxY); // 应用新位置 CGRect newFrame = originalFrame; newFrame.origin.y = newY; self.metalienKK_popupView.frame = newFrame; // 计算背景视图的高度变化(跟随拖拽) CGFloat backgroundHeight = MetalienKK_kMiniPlayerViewHeight + (newY - minY); [self.metalienKK_playerView mas_updateConstraints:^(MASConstraintMaker *make) { make.height.mas_equalTo(backgroundHeight); }]; [self layoutIfNeeded]; break; } case UIGestureRecognizerStateEnded: case UIGestureRecognizerStateCancelled: { CGFloat currentY = self.metalienKK_popupView.frame.origin.y; CGFloat threshold = MetalienKK_ScreenHeight * 0.5; // 阈值设为屏幕高度的50% BOOL shouldClose = NO; // 根据速度或位置决定操作 if (velocity.y > 800) { // 快速下滑则关闭 shouldClose = YES; } else if (velocity.y < -800) { // 快速上滑则展开 shouldClose = NO; } else { // 根据位置决定 shouldClose = (currentY > threshold); } if (shouldClose) { [self metalienKK_closePopupView]; } else { [self metalienKK_showPopupViewAtIndex:-999]; } break; } default: break; } } #pragma mark - Action /// 全屏播放 - (void)metalienKK_fullScreenButtonAction:(UIButton *)sender { // 确保使用主线程执行旋转 dispatch_async(dispatch_get_main_queue(), ^{ if ([self.delegate respondsToSelector:@selector(metalienKK_fullScreenOpenButtonDidClick:)]) { [self.delegate metalienKK_fullScreenOpenButtonDidClick:sender]; } }); } /// 评论 - (void)metalienKK_commentButtonAction:(UIButton *)sender { [self metalienKK_showPopupViewAtIndex:1]; } /// 赞 & 取消赞 - (void)metalienKK_likeButtonAction:(UIButton *)sender { } /// 棒 & 取消棒 - (void)metalienKK_wowButtonAction:(UIButton *)sender { } # pragma mark - TTVideoEngineDelegate /// 播放停止回调 - (void)videoEngineUserStopped:(TTVideoEngine *)videoEngine { MetalienKK_Log(@"videoEngineUserStopped:"); } /// 播放结束回调 - (void)videoEngineDidFinish:(TTVideoEngine *)videoEngine error:(nullable NSError *)error { MetalienKK_Log(@"videoEngineDidFinish:error:"); } /// 异常播放结束回调 - (void)videoEngineDidFinish:(TTVideoEngine *)videoEngine videoStatusException:(NSInteger)status { MetalienKK_Log(@"videoEngineDidFinish:videoStatusException:"); } /// 播放器实例销毁回调 - (void)videoEngineCloseAysncFinish:(TTVideoEngine *)videoEngine { MetalienKK_Log(@"videoEngineCloseAysncFinish:"); } /// 播放状态改变回调 - (void)videoEngine:(TTVideoEngine *)videoEngine playbackStateDidChanged:(TTVideoEnginePlaybackState)playbackState { MetalienKK_Log(@"videoEngine:playbackStateDidChanged:"); self.responseData.isPlaying = (playbackState == TTVideoEnginePlaybackStatePlaying); self.metalienKK_progressView.isPlaying = self.responseData.isPlaying; // 显示&隐藏控件 [self metalienKK_showAndHiddenControls]; } /// 加载状态改变回调 - (void)videoEngine:(TTVideoEngine *)videoEngine loadStateDidChanged:(TTVideoEngineLoadState)loadState { MetalienKK_Log(@"videoEngine:loadStateDidChanged:"); if (loadState == TTVideoEngineLoadStatePlayable) { self.responseData.isLoading = NO; [self.metalienKK_progressView metalienKK_shortVideoStopLoadingAnimation]; } else { self.responseData.isLoading = YES; [self.metalienKK_progressView metalienKK_shortVideoStartLoadingAnimation]; } } /// 播放器各模块准备完成、可以播放回调 - (void)videoEnginePrepared:(TTVideoEngine *)videoEngine { MetalienKK_Log(@"videoEnginePrepared:"); self.metalienKK_progressView.duration = videoEngine.duration; } /// 视频加载完成、开始播放回调 - (void)videoEngineReadyToPlay:(TTVideoEngine *)videoEngine { MetalienKK_Log(@"videoEngineReadyToPlay:"); } /// 显示视频首帧回调 - (void)videoEngineReadyToDisPlay:(TTVideoEngine *)videoEngine { MetalienKK_Log(@"videoEngineReadyToDisPlay:(width:%ld, height:%ld)", self.responseData.videoWidth, self.responseData.videoHeight); self.responseData.isReadyDisplay = YES; self.metalienKK_loadingView.hidden = YES; } # pragma mark - TTVideoEngineResolutionDelegate /// 视频分辨率发生变化回调 - (void)videoSizeDidChange:(TTVideoEngine *)videoEngine videoWidth:(NSInteger)videoWidth videoHeight:(NSInteger)videoHeight { MetalienKK_Log(@"videoSizeDidChange:videoWidth:%ld videoHeight:%ld", (long)videoWidth, (long)videoHeight); // 缓存信息 self.responseData.videoWidth = videoWidth; self.responseData.videoHeight = videoHeight; // 按钮显示状态 self.responseData.isHorizontalResolution = (self.responseData.videoWidth > self.responseData.videoHeight); self.metalienKK_fullScreenButton.hidden = !self.responseData.isHorizontalResolution; // 重置约束 [self metalienKK_remarkConstraints]; } # pragma mark - Override - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (self.responseData.isShowingPopupView) { CGPoint commentPoint = [self.metalienKK_popupView convertPoint:point fromView:self]; if ([self.metalienKK_popupView pointInside:commentPoint withEvent:event]) { return [self.metalienKK_popupView hitTest:commentPoint withEvent:event]; } CGPoint maskPoint = [self.metalienKK_maskView convertPoint:point fromView:self]; if ([self.metalienKK_maskView pointInside:maskPoint withEvent:event]) { return self.metalienKK_maskView; } return self; } return [super hitTest:point withEvent:event]; } //# pragma mark - Helper // ///// 判断视图是否存在 //- (BOOL)metalienKK_isVideoEnginePlayerViewExist { // return [self.metalienKK_videoEngine.playerView isDescendantOfView:self.metalienKK_playerView]; //} # pragma mark - Getter - (TTVideoEngine *)metalienKK_videoEngine { if (!_metalienKK_videoEngine) { _metalienKK_videoEngine = [[TTVideoEngine alloc] initWithOwnPlayer:YES]; _metalienKK_videoEngine.delegate = self; _metalienKK_videoEngine.resolutionDelegate = self; _metalienKK_videoEngine.looping = YES; [_metalienKK_videoEngine setOptionForKey:VEKKeyViewScaleMode_ENUM value:@(TTVideoEngineScalingModeAspectFit)]; [_metalienKK_videoEngine setOptionForKey:VEKKeyAudioChannelEffect_ENUM value:@(TTVideoEngineAudioChannelNormal)]; // 播放 DASH 视频 [_metalienKK_videoEngine setOptionForKey:VEKKeyPlayerBashEnabled_BOOL value:@(YES)]; [_metalienKK_videoEngine setOptionForKey:VEKKeyPlayerDashEnabled_BOOL value:@(YES)]; } return _metalienKK_videoEngine; } - (MetalienKKShortVideoProgressView *)metalienKK_progressView { if (!_metalienKK_progressView) { // 弱引用 MetalienKK_WeakSelf(self); // 初始化 _metalienKK_progressView = [[MetalienKKShortVideoProgressView alloc] initWithFrame:CGRectMake(0, 0, MetalienKK_kProgressViewSize.width, MetalienKK_kProgressViewSize.height)]; [_metalienKK_progressView setOnSeek:^(NSInteger time) { // 强引用 MetalienKK_StrongSelf(self); // 跳转到指定的时间位置 [self.metalienKK_videoEngine setCurrentPlaybackTime:time complete:^(BOOL success) { // Nothing ... }]; }]; [_metalienKK_progressView metalienKK_shortVideoStartLoadingAnimation]; } return _metalienKK_progressView; } - (MetalienKKShortVideoContentPreviewView *)metalienKK_contentView { if (!_metalienKK_contentView) { _metalienKK_contentView = [[MetalienKKShortVideoContentPreviewView alloc] init]; _metalienKK_contentView.userInteractionEnabled = YES; // 添加手势 UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(metalienKK_contentViewTapGesture:)]; [_metalienKK_contentView addGestureRecognizer:tap]; } return _metalienKK_contentView; } - (MetalienKKShortVideoUserView *)metalienKK_userView { if (!_metalienKK_userView) { _metalienKK_userView = [[MetalienKKShortVideoUserView alloc] init]; } return _metalienKK_userView; } - (UIView *)metalienKK_playerView { if (!_metalienKK_playerView) { _metalienKK_playerView = [[UIView alloc] init]; _metalienKK_playerView.userInteractionEnabled = YES; } return _metalienKK_playerView; } - (UIView *)metalienKK_bottomView { if (!_metalienKK_bottomView) { _metalienKK_bottomView = [[UIView alloc] init]; _metalienKK_bottomView.backgroundColor = MetalienKK_ColorHex(@"#11111D"); } return _metalienKK_bottomView; } - (UIButton *)metalienKK_commentButton { if (!_metalienKK_commentButton) { _metalienKK_commentButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_metalienKK_commentButton.titleLabel setFont:MetalienKK_BoldFont(14)]; [_metalienKK_commentButton setTitle:MetalienKK_LocalizedString(@"评论") forState:UIControlStateNormal]; [_metalienKK_commentButton setTitleColor:MetalienKK_ColorHex(@"#E6EBF4") forState:UIControlStateNormal]; [_metalienKK_commentButton setImage:[UIImage imageNamed:@"metalienKKImage_shortVideo_comment"] forState:UIControlStateNormal]; [_metalienKK_commentButton addTarget:self action:@selector(metalienKK_commentButtonAction:) forControlEvents:UIControlEventTouchUpInside]; } return _metalienKK_commentButton; } - (UIButton *)metalienKK_likeButton { if (!_metalienKK_likeButton) { _metalienKK_likeButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_metalienKK_likeButton.titleLabel setFont:MetalienKK_BoldFont(14)]; [_metalienKK_likeButton setTitle:MetalienKK_LocalizedString(@"赞") forState:UIControlStateNormal]; [_metalienKK_likeButton setTitleColor:MetalienKK_ColorHex(@"#E6EBF4") forState:UIControlStateNormal]; [_metalienKK_likeButton setImage:[UIImage imageNamed:@"metalienKKImage_shortVideo_like"] forState:UIControlStateNormal]; [_metalienKK_likeButton addTarget:self action:@selector(metalienKK_likeButtonAction:) forControlEvents:UIControlEventTouchUpInside]; } return _metalienKK_likeButton; } - (UIButton *)metalienKK_wowButton { if (!_metalienKK_wowButton) { _metalienKK_wowButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_metalienKK_wowButton.titleLabel setFont:MetalienKK_BoldFont(14)]; [_metalienKK_wowButton setTitle:MetalienKK_LocalizedString(@"棒") forState:UIControlStateNormal]; [_metalienKK_wowButton setTitleColor:MetalienKK_ColorHex(@"#E6EBF4") forState:UIControlStateNormal]; [_metalienKK_wowButton setImage:[UIImage imageNamed:@"metalienKKImage_shortVideo_WOW"] forState:UIControlStateNormal]; [_metalienKK_wowButton addTarget:self action:@selector(metalienKK_wowButtonAction:) forControlEvents:UIControlEventTouchUpInside]; } return _metalienKK_wowButton; } - (UIView *)metalienKK_bottomLineView_1 { if (!_metalienKK_bottomLineView_1) { _metalienKK_bottomLineView_1 = [[UIView alloc] init]; _metalienKK_bottomLineView_1.backgroundColor = MetalienKK_ColorHexAlpha(@"#848C96", 0.1); } return _metalienKK_bottomLineView_1; } - (UIView *)metalienKK_bottomLineView_2 { if (!_metalienKK_bottomLineView_2) { _metalienKK_bottomLineView_2 = [[UIView alloc] init]; _metalienKK_bottomLineView_2.backgroundColor = MetalienKK_ColorHexAlpha(@"#848C96", 0.1); } return _metalienKK_bottomLineView_2; } - (UIImageView *)metalienKK_playImageView { if (!_metalienKK_playImageView) { _metalienKK_playImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"metalienKKImage_shortVideoPlay"]]; _metalienKK_playImageView.hidden = YES; } return _metalienKK_playImageView; } - (UIView *)metalienKK_maskView { if (!_metalienKK_maskView) { _metalienKK_maskView = [[UIView alloc] init]; _metalienKK_maskView.backgroundColor = UIColor.clearColor; _metalienKK_maskView.userInteractionEnabled = YES; // 添加手势 UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(metalienKK_maskViewTapGesture:)]; [_metalienKK_maskView addGestureRecognizer:tap]; } return _metalienKK_maskView; } - (UIPanGestureRecognizer *)metalienKK_maskViewBlockPanGesture { if (!_metalienKK_maskViewBlockPanGesture) { _metalienKK_maskViewBlockPanGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(metalienKK_maskViewPanGesture:)]; } return _metalienKK_maskViewBlockPanGesture; } - (UIPanGestureRecognizer *)metalienKK_popupViewPanGesture { if (!_metalienKK_popupViewPanGesture) { _metalienKK_popupViewPanGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(metalienKK_popupViewPanGesture:)]; } return _metalienKK_popupViewPanGesture; } - (MetalienKKShortVideoPopupView *)metalienKK_popupView { if (!_metalienKK_popupView) { // 初始化视图 _metalienKK_popupView = [[MetalienKKShortVideoPopupView alloc] init]; _metalienKK_popupView.backgroundColor = [UIColor whiteColor]; _metalienKK_popupView.clipsToBounds = YES; _metalienKK_popupView.layer.cornerRadius = 12.0; _metalienKK_popupView.hidden = YES; _metalienKK_popupView.parentViewPanGestureRecognizer = self.metalienKK_popupViewPanGesture; // 添加拖拽手势 [_metalienKK_popupView addGestureRecognizer:self.metalienKK_popupViewPanGesture]; // 添加按钮事件 [_metalienKK_popupView.metalienKK_commentView.metalienKK_closeButton addTarget:self action:@selector(metalienKK_closePopupView) forControlEvents:UIControlEventTouchUpInside]; } return _metalienKK_popupView; } - (UIButton *)metalienKK_fullScreenButton { if (!_metalienKK_fullScreenButton) { _metalienKK_fullScreenButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_metalienKK_fullScreenButton setFrame:CGRectMake(0, 0, MetalienKK_kFullScreenDefaultSize.width, MetalienKK_kFullScreenDefaultSize.height)]; [_metalienKK_fullScreenButton.titleLabel setFont:MetalienKK_FullScreenOpenButtonFont]; [_metalienKK_fullScreenButton setTitle:MetalienKK_LocalizedString(@"全屏播放") forState:UIControlStateNormal]; [_metalienKK_fullScreenButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal]; [_metalienKK_fullScreenButton setImage:[UIImage imageNamed:@"metalienKKImage_shortVideo_fullScreen_open"] forState:UIControlStateNormal]; [_metalienKK_fullScreenButton setClipsToBounds:YES]; [_metalienKK_fullScreenButton.layer setCornerRadius:MetalienKK_kFullScreenDefaultSize.height/2.0]; [_metalienKK_fullScreenButton.layer setBorderColor:MetalienKK_ColorHexAlpha(@"#FFFFFF", 0.15).CGColor]; [_metalienKK_fullScreenButton.layer setBorderWidth:1.0]; [_metalienKK_fullScreenButton addTarget:self action:@selector(metalienKK_fullScreenButtonAction:) forControlEvents:UIControlEventTouchUpInside]; [_metalienKK_fullScreenButton metalienKK_layoutWithStyle:MetalienKKButtonEdgeInsetsStyleImageLeft andSpace:8.0]; [_metalienKK_fullScreenButton setHidden:YES]; } return _metalienKK_fullScreenButton; } - (UILabel *)metalienKK_durationLabel { if (!_metalienKK_durationLabel) { _metalienKK_durationLabel = [[UILabel alloc] init]; _metalienKK_durationLabel.font = MetalienKK_RegularFont(14); _metalienKK_durationLabel.text = @"00:00 / 00:00"; _metalienKK_durationLabel.textColor = MetalienKK_ColorHexAlpha(@"#A8B0B9", 0.7); _metalienKK_durationLabel.hidden = YES; } return _metalienKK_durationLabel; } - (UIView *)metalienKK_loadingView { if (!_metalienKK_loadingView) { _metalienKK_loadingView = [[UIView alloc] init]; _metalienKK_loadingView.hidden = YES; // 添加图标 UIImageView * iconImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"metalienKKImage_metalienIconBig"]]; iconImageView.contentMode = UIViewContentModeScaleAspectFit; [_metalienKK_loadingView addSubview:iconImageView]; [iconImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.centerX.equalTo(_metalienKK_loadingView); make.size.mas_equalTo(CGSizeMake(40, 40)); }]; // 添加说明 UILabel * tipLabel = [[UILabel alloc] init]; tipLabel.font = MetalienKK_RegularFont(12); tipLabel.text = MetalienKK_LocalizedString(@"加载中..."); tipLabel.textColor = MetalienKK_ColorHexAlpha(@"#848C96", 0.6); [_metalienKK_loadingView addSubview:tipLabel]; [tipLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(_metalienKK_loadingView); make.bottom.equalTo(_metalienKK_loadingView); }]; } return _metalienKK_loadingView; } # pragma mark - Dealloc - (void)dealloc { // 移除通知 [[NSNotificationCenter defaultCenter] removeObserver:self]; // 销毁播放器 [self.metalienKK_videoEngine stop]; [self.metalienKK_videoEngine removeTimeObserver]; [self.metalienKK_videoEngine.playerView removeFromSuperview]; [self.metalienKK_videoEngine closeAysnc]; self.metalienKK_videoEngine = nil; } /* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect { // Drawing code } */ @end 旋转视图时,为什么播放器的视频会黑一下,再显示出来的?
最新发布
07-17
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值