Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
G
galaxy-iOS
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
mobile-group
galaxy-iOS
Commits
e0ed7074
Commit
e0ed7074
authored
Sep 28, 2025
by
Alex朱枝文
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
社区相关功能模块开发
parent
59b41db5
Changes
27
Show whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
2146 additions
and
37 deletions
+2146
-37
TUILogin.m
TUIKit/TUICore/TUILogin.m
+2
-1
project.pbxproj
galaxy/galaxy.xcodeproj/project.pbxproj
+32
-0
YHNavigationController.swift
galaxy/galaxy/Classes/Base/C/YHNavigationController.swift
+1
-1
YHCirclePublishViewController.swift
...ty(社区)/Circle(下属社区)/C/YHCirclePublishViewController.swift
+436
-25
YHCircleAddPhotoCell.swift
...s/Community(社区)/Circle(下属社区)/V/YHCircleAddPhotoCell.swift
+46
-0
YHCircleMediaCell.swift
...ules/Community(社区)/Circle(下属社区)/V/YHCircleMediaCell.swift
+192
-0
YHCirclePhotoCell.swift
...ules/Community(社区)/Circle(下属社区)/V/YHCirclePhotoCell.swift
+63
-0
YHMediaUploadSheetView.swift
...Community(社区)/Circle(下属社区)/V/YHMediaUploadSheetView.swift
+546
-0
YHCertificateUploadSheetView.swift
...MyCertificates(我的证书)/V/YHCertificateUploadSheetView.swift
+25
-2
YHDocumentUploadView.swift
...)/QMAS(优才)/MyDocuments(我的文书)/V/YHDocumentUploadView.swift
+25
-2
YHMediaBrowserViewController.swift
...es/PictureReview(图片预览)/YHMediaBrowserViewController.swift
+78
-0
YHPictureReviewManager.swift
.../Modules/PictureReview(图片预览)/YHPictureReviewManager.swift
+229
-6
YHPreviewMediaItem.swift
...sses/Modules/PictureReview(图片预览)/YHPreviewMediaItem.swift
+72
-0
YHSelectMediaItem.swift
...asses/Modules/PictureReview(图片预览)/YHSelectMediaItem.swift
+30
-0
YHVideoCell.swift
...axy/Classes/Modules/PictureReview(图片预览)/YHVideoCell.swift
+281
-0
Contents.json
...Community/Circle/circle_pause_icon.imageset/Contents.json
+22
-0
circle_pause_icon@2x.png
...ircle/circle_pause_icon.imageset/circle_pause_icon@2x.png
+0
-0
circle_pause_icon@3x.png
...ircle/circle_pause_icon.imageset/circle_pause_icon@3x.png
+0
-0
Contents.json
.../Community/Circle/circle_play_icon.imageset/Contents.json
+22
-0
circle_play_icon@2x.png
.../Circle/circle_play_icon.imageset/circle_play_icon@2x.png
+0
-0
circle_play_icon@3x.png
.../Circle/circle_play_icon.imageset/circle_play_icon@3x.png
+0
-0
Contents.json
.../Community/Circle/circle_plus_icon.imageset/Contents.json
+22
-0
circle_plus_icon@2x.png
.../Circle/circle_plus_icon.imageset/circle_plus_icon@2x.png
+0
-0
circle_plus_icon@3x.png
.../Circle/circle_plus_icon.imageset/circle_plus_icon@3x.png
+0
-0
Contents.json
...mmunity/Circle/media_brower_delete.imageset/Contents.json
+22
-0
media_brower_delete@2x.png
...e/media_brower_delete.imageset/media_brower_delete@2x.png
+0
-0
media_brower_delete@3x.png
...e/media_brower_delete.imageset/media_brower_delete@3x.png
+0
-0
No files found.
TUIKit/TUICore/TUILogin.m
View file @
e0ed7074
...
...
@@ -254,7 +254,8 @@ NSString *const TUILogoutFailNotification = @"TUILogoutFailNotification";
[
NSNotificationCenter
.
defaultCenter
postNotificationName
:
TUILoginSuccessNotification
object
:
nil
];
}
fail
:^
(
int
code
,
NSString
*
desc
)
{
self
.
loginWithInit
=
NO
;
__strong
__typeof
(
weakSelf
)
strongSelf
=
weakSelf
;
strongSelf
.
loginWithInit
=
NO
;
if
(
fail
)
{
fail
(
code
,
desc
);
}
...
...
galaxy/galaxy.xcodeproj/project.pbxproj
View file @
e0ed7074
...
...
@@ -1292,6 +1292,14 @@
04D8FFB62DAE489A00703C75
/* YHVisitHKAlertView.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04D8FFB52DAE489A00703C75
/* YHVisitHKAlertView.swift */
;
};
04D8FFB82DB0D50B00703C75
/* YHGalaxyNewsListViewController.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04D8FFB72DB0D50B00703C75
/* YHGalaxyNewsListViewController.swift */
;
};
04D8FFBA2DB0D95A00703C75
/* YHGalaxyNewsCell.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04D8FFB92DB0D95A00703C75
/* YHGalaxyNewsCell.swift */
;
};
04E0D3C82E866A6300F1824B
/* YHCirclePhotoCell.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E0D3C72E866A6300F1824B
/* YHCirclePhotoCell.swift */
;
};
04E0D3CA2E866A9800F1824B
/* YHCircleAddPhotoCell.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E0D3C92E866A9800F1824B
/* YHCircleAddPhotoCell.swift */
;
};
04E0D3CC2E877D4D00F1824B
/* YHMediaUploadSheetView.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E0D3CB2E877D4D00F1824B
/* YHMediaUploadSheetView.swift */
;
};
04E0D3CE2E87980E00F1824B
/* YHCircleMediaCell.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E0D3CD2E87980D00F1824B
/* YHCircleMediaCell.swift */
;
};
04E0D3D02E87C5C700F1824B
/* YHMediaBrowserViewController.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E0D3CF2E87C5C700F1824B
/* YHMediaBrowserViewController.swift */
;
};
04E0D3D22E87CCB300F1824B
/* YHVideoCell.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E0D3D12E87CCB300F1824B
/* YHVideoCell.swift */
;
};
04E0D3D42E87CF3200F1824B
/* YHPreviewMediaItem.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E0D3D32E87CF3200F1824B
/* YHPreviewMediaItem.swift */
;
};
04E0D3D62E87CF7400F1824B
/* YHSelectMediaItem.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E0D3D52E87CF7400F1824B
/* YHSelectMediaItem.swift */
;
};
04E4CF3E2D5C6D32004D4013
/* YHCountryMessageView.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E4CF3D2D5C6D32004D4013
/* YHCountryMessageView.swift */
;
};
04E4CF402D5C83AE004D4013
/* YHSelectPhoneCountryViewController.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E4CF3F2D5C83AE004D4013
/* YHSelectPhoneCountryViewController.swift */
;
};
04E507D62D6EE856005F758B
/* YHUserLevelAlertView.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
04E507D52D6EE856005F758B
/* YHUserLevelAlertView.swift */
;
};
...
...
@@ -2646,6 +2654,14 @@
04D8FFB52DAE489A00703C75
/* YHVisitHKAlertView.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHVisitHKAlertView.swift
;
sourceTree
=
"<group>"
;
};
04D8FFB72DB0D50B00703C75
/* YHGalaxyNewsListViewController.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHGalaxyNewsListViewController.swift
;
sourceTree
=
"<group>"
;
};
04D8FFB92DB0D95A00703C75
/* YHGalaxyNewsCell.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHGalaxyNewsCell.swift
;
sourceTree
=
"<group>"
;
};
04E0D3C72E866A6300F1824B
/* YHCirclePhotoCell.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHCirclePhotoCell.swift
;
sourceTree
=
"<group>"
;
};
04E0D3C92E866A9800F1824B
/* YHCircleAddPhotoCell.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHCircleAddPhotoCell.swift
;
sourceTree
=
"<group>"
;
};
04E0D3CB2E877D4D00F1824B
/* YHMediaUploadSheetView.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHMediaUploadSheetView.swift
;
sourceTree
=
"<group>"
;
};
04E0D3CD2E87980D00F1824B
/* YHCircleMediaCell.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHCircleMediaCell.swift
;
sourceTree
=
"<group>"
;
};
04E0D3CF2E87C5C700F1824B
/* YHMediaBrowserViewController.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHMediaBrowserViewController.swift
;
sourceTree
=
"<group>"
;
};
04E0D3D12E87CCB300F1824B
/* YHVideoCell.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHVideoCell.swift
;
sourceTree
=
"<group>"
;
};
04E0D3D32E87CF3200F1824B
/* YHPreviewMediaItem.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHPreviewMediaItem.swift
;
sourceTree
=
"<group>"
;
};
04E0D3D52E87CF7400F1824B
/* YHSelectMediaItem.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHSelectMediaItem.swift
;
sourceTree
=
"<group>"
;
};
04E4CF3D2D5C6D32004D4013
/* YHCountryMessageView.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHCountryMessageView.swift
;
sourceTree
=
"<group>"
;
};
04E4CF3F2D5C83AE004D4013
/* YHSelectPhoneCountryViewController.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHSelectPhoneCountryViewController.swift
;
sourceTree
=
"<group>"
;
};
04E507D52D6EE856005F758B
/* YHUserLevelAlertView.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
YHUserLevelAlertView.swift
;
sourceTree
=
"<group>"
;
};
...
...
@@ -6108,9 +6124,13 @@
045C0F002D12CA5E00BD2DC0
/* PictureReview(图片预览) */
=
{
isa
=
PBXGroup
;
children
=
(
04E0D3D12E87CCB300F1824B
/* YHVideoCell.swift */
,
045C0EFE2D12CA5E00BD2DC0
/* YHLongtapPictureSheetView.swift */
,
045C0EFF2D12CA5E00BD2DC0
/* YHPictureReviewManager.swift */
,
04D8FFB12DA5007A00703C75
/* YHPictureBrowserViewController.swift */
,
04E0D3CF2E87C5C700F1824B
/* YHMediaBrowserViewController.swift */
,
04E0D3D32E87CF3200F1824B
/* YHPreviewMediaItem.swift */
,
04E0D3D52E87CF7400F1824B
/* YHSelectMediaItem.swift */
,
);
path
=
"PictureReview(图片预览)"
;
sourceTree
=
"<group>"
;
...
...
@@ -6963,7 +6983,11 @@
04D4EC2A2E839B3000B0329B
/* V */
=
{
isa
=
PBXGroup
;
children
=
(
04E0D3CD2E87980D00F1824B
/* YHCircleMediaCell.swift */
,
04E0D3CB2E877D4D00F1824B
/* YHMediaUploadSheetView.swift */
,
04D4EC4D2E84F22500B0329B
/* YHCircleHeaderReusableView.swift */
,
04E0D3C92E866A9800F1824B
/* YHCircleAddPhotoCell.swift */
,
04E0D3C72E866A6300F1824B
/* YHCirclePhotoCell.swift */
,
04D4EC452E83D11500B0329B
/* YHCircleCollectionViewCell.swift */
,
);
path
=
V
;
...
...
@@ -7898,6 +7922,7 @@
045C10E22D12CA5F00BD2DC0
/* YHInvitationWithGiftsDetailView.swift in Sources */
,
045C10E32D12CA5F00BD2DC0
/* YHRiskWarningCell.swift in Sources */
,
045C10E42D12CA5F00BD2DC0
/* YHFileRenameInputView.swift in Sources */
,
04E0D3CA2E866A9800F1824B
/* YHCircleAddPhotoCell.swift in Sources */
,
045C10E52D12CA5F00BD2DC0
/* YHLookResignResultFootView.swift in Sources */
,
04AE204E2D1941FC00891D24
/* YHGCCertificateListContainerVC.swift in Sources */
,
045C10E62D12CA5F00BD2DC0
/* YHInvitationWithGiftsViewController.swift in Sources */
,
...
...
@@ -8031,6 +8056,7 @@
045C11522D12CA5F00BD2DC0
/* YHResignAppointSubmitTipsView.swift in Sources */
,
045C11532D12CA5F00BD2DC0
/* YHLookResignResultStateOneTableViewCell.swift in Sources */
,
045C11542D12CA5F00BD2DC0
/* YHWorkExperienceCompanyModel.swift in Sources */
,
04E0D3CE2E87980E00F1824B
/* YHCircleMediaCell.swift in Sources */
,
045C11552D12CA5F00BD2DC0
/* YHResignInfoItemView.swift in Sources */
,
04AE203B2D13C01B00891D24
/* YHGCEducationInfoListVC.swift in Sources */
,
045C11562D12CA5F00BD2DC0
/* YHHUDSquareBaseView.swift in Sources */
,
...
...
@@ -8110,7 +8136,9 @@
045C11942D12CA5F00BD2DC0
/* YHServiceBannerView.swift in Sources */
,
045C11952D12CA5F00BD2DC0
/* YHFourKingViewController.swift in Sources */
,
045C11962D12CA5F00BD2DC0
/* YHActivityTipsItemView.swift in Sources */
,
04E0D3D22E87CCB300F1824B
/* YHVideoCell.swift in Sources */
,
045C11972D12CA5F00BD2DC0
/* YHHUDRotatingImageView.swift in Sources */
,
04E0D3D62E87CF7400F1824B
/* YHSelectMediaItem.swift in Sources */
,
04307B6C2D1A547C00ED8E8D
/* YHIncomeDateTillNowCell.swift in Sources */
,
045C11982D12CA5F00BD2DC0
/* YHCerAppointViewModel.swift in Sources */
,
045C11992D12CA5F00BD2DC0
/* YHPayMembersCell.swift in Sources */
,
...
...
@@ -8210,6 +8238,7 @@
045C11EA2D12CA5F00BD2DC0
/* YHMySignatureDetailModel.swift in Sources */
,
045C11EB2D12CA5F00BD2DC0
/* YHPlayerControlView.swift in Sources */
,
04307B6E2D1A5F4200ED8E8D
/* YHIncomeUploadWorkIDCell.swift in Sources */
,
04E0D3D02E87C5C700F1824B
/* YHMediaBrowserViewController.swift in Sources */
,
045C11EC2D12CA5F00BD2DC0
/* YHMyDocumentsListViewController.swift in Sources */
,
045C11ED2D12CA5F00BD2DC0
/* YHCertificateEntryBottomView.swift in Sources */
,
045C11EE2D12CA5F00BD2DC0
/* YHAppointItem.swift in Sources */
,
...
...
@@ -8239,6 +8268,7 @@
045C12002D12CA5F00BD2DC0
/* YHPrincipleApprovedAlertView.swift in Sources */
,
045C12012D12CA5F00BD2DC0
/* YHSchemeTableHeadView.swift in Sources */
,
045C12022D12CA5F00BD2DC0
/* YHResignDocumentUploadStatus.swift in Sources */
,
04E0D3D42E87CF3200F1824B
/* YHPreviewMediaItem.swift in Sources */
,
045C12032D12CA5F00BD2DC0
/* YHActivityListCell.swift in Sources */
,
045C12042D12CA5F00BD2DC0
/* YHTravelCertificateTipsCell.swift in Sources */
,
045C12052D12CA5F00BD2DC0
/* YHVisaRenewalPayMethodQrcodeCell.swift in Sources */
,
...
...
@@ -8256,6 +8286,7 @@
045C12102D12CA5F00BD2DC0
/* YHBaseViewController.swift in Sources */
,
045C12112D12CA5F00BD2DC0
/* YHInfoItemOptionView.swift in Sources */
,
045C12122D12CA5F00BD2DC0
/* YHPreviewInfoWorkSummaryView.swift in Sources */
,
04E0D3CC2E877D4D00F1824B
/* YHMediaUploadSheetView.swift in Sources */
,
045C12132D12CA5F00BD2DC0
/* YHVisaRenewalPayOccupyingSpaceCell.swift in Sources */
,
045C12142D12CA5F00BD2DC0
/* YHCheckEamilAlertView.swift in Sources */
,
045C12152D12CA5F00BD2DC0
/* YHResignAppointedScheduleRiskTipsView.swift in Sources */
,
...
...
@@ -8650,6 +8681,7 @@
045C13562D12CA5F00BD2DC0
/* YHOtherPickerView.swift in Sources */
,
045C13572D12CA5F00BD2DC0
/* YHLivePlayerViewController.swift in Sources */
,
045C13582D12CA5F00BD2DC0
/* YHAIChatInputShadowView.swift in Sources */
,
04E0D3C82E866A6300F1824B
/* YHCirclePhotoCell.swift in Sources */
,
0411CF002D1A805A00644D35
/* YHGCMySignatureListViewModel.swift in Sources */
,
045C13592D12CA5F00BD2DC0
/* YHMyFileListNoneCell.swift in Sources */
,
045C135A2D12CA5F00BD2DC0
/* YHCardButton.swift in Sources */
,
...
...
galaxy/galaxy/Classes/Base/C/YHNavigationController.swift
View file @
e0ed7074
...
...
@@ -39,7 +39,7 @@ class YHNavigationController: UINavigationController {
if
let
lastVC
=
viewControllers
.
last
{
let
className
=
String
(
describing
:
type
(
of
:
lastVC
))
if
!
className
.
hasPrefix
(
"TUI"
)
{
// 模糊匹配类名,使得腾讯IM页面不用隐藏NavigationBar
var
needAnimated
=
false
var
needAnimated
=
animated
let
lastSecondCount
=
viewControllers
.
count
-
2
if
lastSecondCount
>=
0
{
let
lastSecondVC
=
viewControllers
[
lastSecondCount
]
...
...
galaxy/galaxy/Classes/Modules/Community(社区)/Circle(下属社区)/C/YHCirclePublishViewController.swift
View file @
e0ed7074
...
...
@@ -7,48 +7,459 @@
//
import
UIKit
import
SnapKit
class
YHCirclePublishViewController
:
YHBaseViewController
{
private
let
marginX
:
CGFloat
=
24
private
let
itemSpace
:
CGFloat
=
8
var
completion
:
(()
->
Void
)?
// MARK: - Navigation Items
private
lazy
var
leftBarItem
:
UIBarButtonItem
=
{
let
item
=
UIBarButtonItem
(
image
:
UIImage
(
named
:
"nav_black_24"
)?
.
withRenderingMode
(
.
alwaysOriginal
),
style
:
.
plain
,
target
:
self
,
action
:
#selector(
cancelButtonTapped
)
)
return
item
}()
private
lazy
var
rightButton
:
UIButton
=
{
let
button
=
UIButton
(
type
:
.
custom
)
button
.
frame
=
CGRect
(
x
:
0
,
y
:
0
,
width
:
56
,
height
:
32
)
button
.
layer
.
cornerRadius
=
16
button
.
clipsToBounds
=
true
button
.
setTitle
(
"发布"
,
for
:
.
normal
)
button
.
titleLabel
?
.
font
=
UIFont
.
PFSC_M
(
ofSize
:
12
)
button
.
setTitleColor
(
UIColor
.
brandGrayColor0
,
for
:
.
normal
)
button
.
setTitleColor
(
UIColor
.
brandGrayColor0
,
for
:
.
disabled
)
button
.
isEnabled
=
false
button
.
backgroundColor
=
UIColor
.
brandGrayColor4
button
.
addTarget
(
self
,
action
:
#selector(
publishButtonTapped
)
,
for
:
.
touchUpInside
)
return
button
}()
// MARK: - UI Components
private
lazy
var
scrollView
:
UIScrollView
=
{
let
scrollView
=
UIScrollView
()
scrollView
.
backgroundColor
=
.
white
scrollView
.
showsVerticalScrollIndicator
=
false
return
scrollView
}()
private
lazy
var
contentView
:
UIView
=
{
let
view
=
UIView
()
view
.
backgroundColor
=
.
white
return
view
}()
private
lazy
var
userInfoView
:
UIView
=
{
let
view
=
UIView
()
view
.
backgroundColor
=
.
white
return
view
}()
private
lazy
var
avatarImageView
:
UIImageView
=
{
let
imageView
=
UIImageView
()
imageView
.
image
=
UIImage
(
named
:
"people_head_default"
)
// 设置默认头像
imageView
.
layer
.
cornerRadius
=
17
imageView
.
clipsToBounds
=
true
imageView
.
contentMode
=
.
scaleAspectFill
return
imageView
}()
private
lazy
var
usernameLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
text
=
"Monica杨晓丽"
label
.
font
=
UIFont
.
PFSC_R
(
ofSize
:
14
)
label
.
textColor
=
.
brandGrayColor8
return
label
}()
private
lazy
var
subtitleLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
text
=
"董事长"
label
.
font
=
UIFont
.
PFSC_R
(
ofSize
:
11
)
label
.
textColor
=
UIColor
.
brandGrayColor6
return
label
}()
private
lazy
var
textView
:
UITextView
=
{
let
textView
=
UITextView
()
textView
.
font
=
UIFont
.
PFSC_B
(
ofSize
:
17
)
textView
.
textColor
=
.
brandGrayColor8
textView
.
backgroundColor
=
.
clear
textView
.
delegate
=
self
textView
.
textContainer
.
lineFragmentPadding
=
0
textView
.
textContainerInset
=
UIEdgeInsets
.
zero
textView
.
showsVerticalScrollIndicator
=
false
return
textView
}()
private
lazy
var
placeholderLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
text
=
"添加标题"
label
.
font
=
UIFont
.
PFSC_R
(
ofSize
:
17
)
label
.
textColor
=
UIColor
.
brandGrayColor5
return
label
}()
private
lazy
var
detailTextView
:
UITextView
=
{
let
textView
=
UITextView
()
textView
.
font
=
UIFont
.
PFSC_R
(
ofSize
:
14
)
textView
.
textColor
=
UIColor
.
brandGrayColor8
textView
.
backgroundColor
=
.
clear
textView
.
delegate
=
self
textView
.
textContainer
.
lineFragmentPadding
=
0
textView
.
textContainerInset
=
UIEdgeInsets
.
zero
textView
.
showsVerticalScrollIndicator
=
false
return
textView
}()
private
lazy
var
detailPlaceholderLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
text
=
"分享生活,表达想法,随时随地..."
label
.
font
=
UIFont
.
PFSC_R
(
ofSize
:
14
)
label
.
textColor
=
UIColor
.
brandGrayColor5
label
.
numberOfLines
=
0
return
label
}()
private
lazy
var
mediaCollectionView
:
UICollectionView
=
{
let
layout
=
UICollectionViewFlowLayout
()
layout
.
minimumInteritemSpacing
=
itemSpace
layout
.
minimumLineSpacing
=
itemSpace
let
itemWidth
=
getItemWidth
()
layout
.
itemSize
=
CGSize
(
width
:
itemWidth
,
height
:
itemWidth
)
let
collectionView
=
UICollectionView
(
frame
:
.
zero
,
collectionViewLayout
:
layout
)
collectionView
.
backgroundColor
=
.
white
collectionView
.
delegate
=
self
collectionView
.
dataSource
=
self
collectionView
.
register
(
YHCircleMediaCell
.
self
,
forCellWithReuseIdentifier
:
"YHCircleMediaCell"
)
collectionView
.
register
(
YHCircleAddPhotoCell
.
self
,
forCellWithReuseIdentifier
:
"YHCircleAddPhotoCell"
)
collectionView
.
showsVerticalScrollIndicator
=
false
return
collectionView
}()
private
lazy
var
divideLine
:
UIView
=
{
let
view
=
UIView
()
view
.
backgroundColor
=
UIColor
.
brandGrayColor3
return
view
}()
// MARK: - Properties
private
var
mediaItems
:
[
YHSelectMediaItem
]
=
[]
private
let
maxMediaCount
=
9
override
func
viewDidLoad
()
{
super
.
viewDidLoad
()
setupNav
()
setupUI
()
setupConstraints
()
setupNotifications
()
}
deinit
{
NotificationCenter
.
default
.
removeObserver
(
self
)
printLog
(
"YHCirclePublishViewController deinit"
)
}
// MARK: - Setup Methods
private
func
setupNav
()
{
gk_navTitle
=
"发布动态"
gk_navLeftBarButtonItem
=
leftBarItem
gk_navRightBarButtonItem
=
UIBarButtonItem
(
customView
:
rightButton
)
gk_navItemRightSpace
=
16
gk_navItemLeftSpace
=
16
gk_navBackgroundColor
=
.
white
}
private
func
setupUI
()
{
view
.
backgroundColor
=
.
white
gk_navigationBar
.
isHidden
=
true
title
=
"发布动态"
navigationItem
.
leftBarButtonItem
=
UIBarButtonItem
(
title
:
"取消"
,
style
:
.
plain
,
target
:
self
,
action
:
#selector(
cancelButtonTapped
)
)
navigationItem
.
rightBarButtonItem
=
UIBarButtonItem
(
title
:
"发布"
,
style
:
.
done
,
target
:
self
,
action
:
#selector(
publishButtonTapped
)
)
let
label
=
UILabel
()
label
.
text
=
"发布页面"
label
.
textAlignment
=
.
center
label
.
textColor
=
.
gray
view
.
addSubview
(
label
)
view
.
addSubview
(
scrollView
)
scrollView
.
addSubview
(
contentView
)
contentView
.
addSubview
(
userInfoView
)
userInfoView
.
addSubview
(
avatarImageView
)
userInfoView
.
addSubview
(
usernameLabel
)
userInfoView
.
addSubview
(
subtitleLabel
)
label
.
snp
.
makeConstraints
{
make
in
make
.
center
.
equalToSuperview
()
contentView
.
addSubview
(
textView
)
contentView
.
addSubview
(
placeholderLabel
)
contentView
.
addSubview
(
divideLine
)
contentView
.
addSubview
(
detailTextView
)
contentView
.
addSubview
(
detailPlaceholderLabel
)
contentView
.
addSubview
(
mediaCollectionView
)
}
private
func
setupConstraints
()
{
scrollView
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
k_Height_NavigationtBarAndStatuBar
)
make
.
left
.
right
.
bottom
.
equalToSuperview
()
}
contentView
.
snp
.
makeConstraints
{
make
in
make
.
edges
.
equalToSuperview
()
make
.
width
.
equalTo
(
view
.
snp
.
width
)
}
userInfoView
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalToSuperview
()
.
offset
(
20
)
make
.
left
.
right
.
equalToSuperview
()
.
inset
(
marginX
)
}
avatarImageView
.
snp
.
makeConstraints
{
make
in
make
.
left
.
top
.
equalToSuperview
()
make
.
width
.
height
.
equalTo
(
34
)
make
.
bottom
.
equalToSuperview
()
}
usernameLabel
.
snp
.
makeConstraints
{
make
in
make
.
left
.
equalTo
(
avatarImageView
.
snp
.
right
)
.
offset
(
8
)
make
.
top
.
equalTo
(
avatarImageView
.
snp
.
top
)
make
.
right
.
equalToSuperview
()
}
subtitleLabel
.
snp
.
makeConstraints
{
make
in
make
.
left
.
equalTo
(
usernameLabel
)
make
.
bottom
.
equalTo
(
avatarImageView
.
snp
.
bottom
)
make
.
right
.
equalToSuperview
()
}
textView
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
userInfoView
.
snp
.
bottom
)
.
offset
(
24
)
make
.
left
.
right
.
equalToSuperview
()
.
inset
(
marginX
)
make
.
height
.
greaterThanOrEqualTo
(
24
)
}
placeholderLabel
.
snp
.
makeConstraints
{
make
in
make
.
top
.
left
.
equalTo
(
textView
)
}
divideLine
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
textView
.
snp
.
bottom
)
.
offset
(
16
)
make
.
left
.
right
.
equalTo
(
textView
)
make
.
height
.
equalTo
(
1
)
}
detailTextView
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
divideLine
.
snp
.
bottom
)
.
offset
(
16
)
make
.
left
.
right
.
equalToSuperview
()
.
inset
(
marginX
)
make
.
height
.
greaterThanOrEqualTo
(
20
)
}
detailPlaceholderLabel
.
snp
.
makeConstraints
{
make
in
make
.
top
.
left
.
equalTo
(
detailTextView
)
}
mediaCollectionView
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
detailTextView
.
snp
.
bottom
)
.
offset
(
96
)
make
.
left
.
right
.
equalToSuperview
()
.
inset
(
marginX
)
make
.
height
.
equalTo
(
200
)
make
.
bottom
.
equalToSuperview
()
.
offset
(
-
30
)
}
updateCollectionViewHeight
()
}
private
func
setupNotifications
()
{
NotificationCenter
.
default
.
addObserver
(
self
,
selector
:
#selector(
keyboardWillShow
)
,
name
:
UIResponder
.
keyboardWillShowNotification
,
object
:
nil
)
NotificationCenter
.
default
.
addObserver
(
self
,
selector
:
#selector(
keyboardWillHide
)
,
name
:
UIResponder
.
keyboardWillHideNotification
,
object
:
nil
)
}
private
func
getItemWidth
()
->
CGFloat
{
let
itemWidth
=
(
UIScreen
.
main
.
bounds
.
width
-
marginX
*
2
-
itemSpace
*
2
)
/
3
return
itemWidth
}
// MARK: - Actions
@objc
private
func
cancelButtonTapped
()
{
if
hasContent
()
{
showCancelAlert
()
}
else
{
dismiss
(
animated
:
true
)
}
}
@objc
private
func
publishButtonTapped
()
{
completion
?()
dismiss
(
animated
:
true
)
// 发布逻辑
showPublishingAlert
()
// 模拟发布延迟
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
2.0
)
{
self
.
completion
?()
self
.
dismiss
(
animated
:
true
)
}
}
// MARK: - Keyboard Handling
@objc
private
func
keyboardWillShow
(
notification
:
NSNotification
)
{
guard
let
keyboardFrame
=
notification
.
userInfo
?[
UIResponder
.
keyboardFrameEndUserInfoKey
]
as?
CGRect
,
let
duration
=
notification
.
userInfo
?[
UIResponder
.
keyboardAnimationDurationUserInfoKey
]
as?
Double
else
{
return
}
let
keyboardHeight
=
keyboardFrame
.
height
UIView
.
animate
(
withDuration
:
duration
)
{
self
.
scrollView
.
contentInset
.
bottom
=
keyboardHeight
self
.
scrollView
.
verticalScrollIndicatorInsets
.
bottom
=
keyboardHeight
}
}
@objc
private
func
keyboardWillHide
(
notification
:
NSNotification
)
{
guard
let
duration
=
notification
.
userInfo
?[
UIResponder
.
keyboardAnimationDurationUserInfoKey
]
as?
Double
else
{
return
}
UIView
.
animate
(
withDuration
:
duration
)
{
self
.
scrollView
.
contentInset
.
bottom
=
0
self
.
scrollView
.
verticalScrollIndicatorInsets
.
bottom
=
0
}
}
// MARK: - Helper Methods
private
func
hasContent
()
->
Bool
{
return
!
textView
.
text
.
isEmpty
||
!
detailTextView
.
text
.
isEmpty
||
!
mediaItems
.
isEmpty
}
private
func
updatePublishButton
()
{
let
hasContent
=
hasContent
()
rightButton
.
isEnabled
=
hasContent
rightButton
.
backgroundColor
=
hasContent
?
UIColor
.
brandGrayColor8
:
UIColor
.
brandGrayColor4
}
private
func
updateCollectionViewHeight
()
{
let
rows
=
ceil
(
Double
(
mediaItems
.
count
+
1
)
/
3.0
)
let
itemWidth
=
getItemWidth
()
let
height
=
CGFloat
(
rows
)
*
itemWidth
+
CGFloat
(
max
(
0
,
rows
-
1
))
*
8
mediaCollectionView
.
snp
.
updateConstraints
{
make
in
make
.
height
.
equalTo
(
max
(
height
,
itemWidth
))
}
}
private
func
showCancelAlert
()
{
let
alert
=
UIAlertController
(
title
:
"确定要离开吗?"
,
message
:
"离开后内容将不会保存"
,
preferredStyle
:
.
alert
)
alert
.
addAction
(
UIAlertAction
(
title
:
"取消"
,
style
:
.
cancel
))
alert
.
addAction
(
UIAlertAction
(
title
:
"确定"
,
style
:
.
destructive
)
{
_
in
self
.
dismiss
(
animated
:
true
)
})
present
(
alert
,
animated
:
true
)
}
private
func
showPublishingAlert
()
{
let
alert
=
UIAlertController
(
title
:
"发布中..."
,
message
:
nil
,
preferredStyle
:
.
alert
)
present
(
alert
,
animated
:
true
)
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
1.5
)
{
alert
.
dismiss
(
animated
:
true
)
}
}
}
// MARK: - UITextViewDelegate
extension
YHCirclePublishViewController
:
UITextViewDelegate
{
func
textViewDidChange
(
_
textView
:
UITextView
)
{
if
textView
==
self
.
textView
{
placeholderLabel
.
isHidden
=
!
textView
.
text
.
isEmpty
}
else
if
textView
==
detailTextView
{
detailPlaceholderLabel
.
isHidden
=
!
textView
.
text
.
isEmpty
}
updatePublishButton
()
}
func
textView
(
_
textView
:
UITextView
,
shouldChangeTextIn
range
:
NSRange
,
replacementText
text
:
String
)
->
Bool
{
if
textView
==
self
.
textView
{
let
currentText
=
textView
.
text
??
""
guard
let
stringRange
=
Range
(
range
,
in
:
currentText
)
else
{
return
false
}
let
updatedText
=
currentText
.
replacingCharacters
(
in
:
stringRange
,
with
:
text
)
return
updatedText
.
count
<=
100
}
return
true
}
}
// MARK: - UICollectionViewDataSource & Delegate
extension
YHCirclePublishViewController
:
UICollectionViewDataSource
,
UICollectionViewDelegate
{
func
collectionView
(
_
collectionView
:
UICollectionView
,
numberOfItemsInSection
section
:
Int
)
->
Int
{
return
min
(
mediaItems
.
count
+
1
,
maxMediaCount
)
}
func
collectionView
(
_
collectionView
:
UICollectionView
,
cellForItemAt
indexPath
:
IndexPath
)
->
UICollectionViewCell
{
if
indexPath
.
item
<
mediaItems
.
count
{
if
let
cell
=
collectionView
.
dequeueReusableCell
(
withReuseIdentifier
:
"YHCircleMediaCell"
,
for
:
indexPath
)
as?
YHCircleMediaCell
{
cell
.
configure
(
with
:
mediaItems
[
indexPath
.
item
])
cell
.
deleteCallback
=
{
[
weak
self
]
in
self
?
.
removeMedia
(
at
:
indexPath
.
item
)
}
return
cell
}
}
else
{
if
let
cell
=
collectionView
.
dequeueReusableCell
(
withReuseIdentifier
:
"YHCircleAddPhotoCell"
,
for
:
indexPath
)
as?
YHCircleAddPhotoCell
{
return
cell
}
}
return
UICollectionViewCell
()
}
func
collectionView
(
_
collectionView
:
UICollectionView
,
didSelectItemAt
indexPath
:
IndexPath
)
{
if
indexPath
.
item
>=
mediaItems
.
count
{
showMediaUploadSheet
()
}
else
{
// 预览本地选中的媒体
previewLocalMedia
(
at
:
indexPath
.
item
)
}
}
// 预览本地选择的媒体文件
private
func
previewLocalMedia
(
at
index
:
Int
)
{
YHPictureReviewManager
.
shared
.
showLocalMedia
(
curIndex
:
index
,
selectMediaItems
:
self
.
mediaItems
)
{
[
weak
self
]
deletedIndex
in
// 删除回调处理
guard
let
self
=
self
else
{
return
}
DispatchQueue
.
main
.
async
{
// 从本地媒体数组中删除对应项
if
deletedIndex
<
self
.
mediaItems
.
count
{
self
.
mediaItems
.
remove
(
at
:
deletedIndex
)
// 刷新集合视图
self
.
mediaCollectionView
.
reloadData
()
self
.
updateCollectionViewHeight
()
self
.
updatePublishButton
()
printLog
(
"已从发布页面删除媒体项,剩余:
\(
self
.
mediaItems
.
count
)
"
)
}
}
}
}
private
func
removeMedia
(
at
index
:
Int
)
{
mediaItems
.
remove
(
at
:
index
)
mediaCollectionView
.
reloadData
()
updateCollectionViewHeight
()
updatePublishButton
()
}
private
func
showMediaUploadSheet
()
{
YHMediaUploadSheetView
.
sheetView
()
.
show
{
[
weak
self
]
selectedMediaItems
in
guard
let
self
=
self
else
{
return
}
// 检查是否超出最大数量限制
let
remainingSlots
=
maxMediaCount
-
mediaItems
.
count
let
itemsToAdd
=
Array
(
selectedMediaItems
.
prefix
(
remainingSlots
))
if
itemsToAdd
.
count
<
selectedMediaItems
.
count
{
YHHUD
.
flash
(
message
:
"最多只能选择
\(
maxMediaCount
)
个媒体文件"
)
}
// 添加新选择的媒体项
mediaItems
.
append
(
contentsOf
:
itemsToAdd
)
mediaCollectionView
.
reloadData
()
updateCollectionViewHeight
()
updatePublishButton
()
print
(
"获得
\(
itemsToAdd
.
count
)
个媒体文件"
)
}
}
}
galaxy/galaxy/Classes/Modules/Community(社区)/Circle(下属社区)/V/YHCircleAddPhotoCell.swift
0 → 100644
View file @
e0ed7074
//
// YHCircleAddPhotoCell.swift
// galaxy
//
// Created by alexzzw on 2025/9/26.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import
UIKit
class
YHCircleAddPhotoCell
:
UICollectionViewCell
{
private
lazy
var
containerView
:
UIView
=
{
let
view
=
UIView
()
view
.
backgroundColor
=
UIColor
.
systemGray6
return
view
}()
private
lazy
var
plusImageView
:
UIImageView
=
{
let
imageView
=
UIImageView
()
imageView
.
image
=
UIImage
(
named
:
"circle_plus_icon"
)
return
imageView
}()
override
init
(
frame
:
CGRect
)
{
super
.
init
(
frame
:
frame
)
setupUI
()
}
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
private
func
setupUI
()
{
contentView
.
addSubview
(
containerView
)
containerView
.
addSubview
(
plusImageView
)
containerView
.
snp
.
makeConstraints
{
make
in
make
.
edges
.
equalToSuperview
()
}
plusImageView
.
snp
.
makeConstraints
{
make
in
make
.
center
.
equalToSuperview
()
make
.
width
.
height
.
equalTo
(
40
)
}
}
}
galaxy/galaxy/Classes/Modules/Community(社区)/Circle(下属社区)/V/YHCircleMediaCell.swift
0 → 100644
View file @
e0ed7074
//
// YHCircleMediaCell.swift
// galaxy
//
// Created by alexzzw on 2025/9/27.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import
UIKit
import
AVFoundation
class
YHCircleMediaCell
:
UICollectionViewCell
{
var
deleteCallback
:
(()
->
Void
)?
// MARK: - UI Components
private
lazy
var
imageView
:
UIImageView
=
{
let
imageView
=
UIImageView
()
imageView
.
contentMode
=
.
scaleAspectFill
imageView
.
clipsToBounds
=
true
imageView
.
backgroundColor
=
UIColor
.
brandGrayColor2
return
imageView
}()
private
lazy
var
deleteButton
:
UIButton
=
{
let
button
=
UIButton
(
type
:
.
custom
)
button
.
setImage
(
UIImage
(
systemName
:
"xmark.circle.fill"
),
for
:
.
normal
)
button
.
tintColor
=
.
white
button
.
backgroundColor
=
UIColor
.
black
.
withAlphaComponent
(
0.5
)
button
.
layer
.
cornerRadius
=
10
button
.
addTarget
(
self
,
action
:
#selector(
deleteButtonTapped
)
,
for
:
.
touchUpInside
)
button
.
isHidden
=
true
return
button
}()
private
lazy
var
playIcon
:
UIImageView
=
{
let
imageView
=
UIImageView
()
imageView
.
image
=
UIImage
(
named
:
"circle_play_icon"
)
imageView
.
isHidden
=
true
return
imageView
}()
private
lazy
var
durationLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
font
=
UIFont
.
systemFont
(
ofSize
:
12
,
weight
:
.
medium
)
label
.
textColor
=
.
white
label
.
backgroundColor
=
UIColor
.
black
.
withAlphaComponent
(
0.5
)
label
.
textAlignment
=
.
center
label
.
layer
.
cornerRadius
=
8
label
.
clipsToBounds
=
true
label
.
isHidden
=
true
return
label
}()
// MARK: - Initialization
override
init
(
frame
:
CGRect
)
{
super
.
init
(
frame
:
frame
)
setupUI
()
}
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
// MARK: - Setup UI
private
func
setupUI
()
{
contentView
.
addSubview
(
imageView
)
contentView
.
addSubview
(
playIcon
)
contentView
.
addSubview
(
durationLabel
)
contentView
.
addSubview
(
deleteButton
)
imageView
.
snp
.
makeConstraints
{
make
in
make
.
edges
.
equalToSuperview
()
}
playIcon
.
snp
.
makeConstraints
{
make
in
make
.
center
.
equalToSuperview
()
make
.
width
.
height
.
equalTo
(
40
)
}
durationLabel
.
snp
.
makeConstraints
{
make
in
make
.
bottom
.
right
.
equalToSuperview
()
.
inset
(
8
)
make
.
height
.
equalTo
(
20
)
make
.
width
.
greaterThanOrEqualTo
(
35
)
}
deleteButton
.
snp
.
makeConstraints
{
make
in
make
.
top
.
right
.
equalToSuperview
()
.
inset
(
4
)
make
.
width
.
height
.
equalTo
(
20
)
}
}
// MARK: - Configure Cell
func
configure
(
with
mediaItem
:
YHSelectMediaItem
)
{
resetCell
()
switch
mediaItem
.
type
{
case
.
image
:
configureForImage
(
mediaItem
)
case
.
video
:
configureForVideo
(
mediaItem
)
}
}
private
func
resetCell
()
{
imageView
.
image
=
nil
playIcon
.
isHidden
=
true
durationLabel
.
isHidden
=
true
durationLabel
.
text
=
nil
}
private
func
configureForImage
(
_
mediaItem
:
YHSelectMediaItem
)
{
imageView
.
image
=
mediaItem
.
image
playIcon
.
isHidden
=
true
durationLabel
.
isHidden
=
true
}
private
func
configureForVideo
(
_
mediaItem
:
YHSelectMediaItem
)
{
// 显示视频相关UI
playIcon
.
isHidden
=
false
// 生成视频缩略图
if
let
videoURL
=
mediaItem
.
videoURL
{
generateVideoThumbnail
(
from
:
videoURL
)
{
[
weak
self
]
thumbnail
in
DispatchQueue
.
main
.
async
{
self
?
.
imageView
.
image
=
thumbnail
}
}
}
// 显示视频时长
// if let duration = mediaItem.duration {
// durationLabel.text = formatDuration(duration)
// durationLabel.isHidden = false
// } else if let videoURL = mediaItem.videoURL {
// // 如果没有时长信息,尝试获取
// getVideoDuration(from: videoURL) { [weak self] duration in
// DispatchQueue.main.async {
// if duration > 0 {
// self?.durationLabel.text = self?.formatDuration(duration)
// self?.durationLabel.isHidden = false
// }
// }
// }
// }
}
// MARK: - Video Helpers
private
func
generateVideoThumbnail
(
from
url
:
URL
,
completion
:
@escaping
(
UIImage
?)
->
Void
)
{
DispatchQueue
.
global
(
qos
:
.
userInitiated
)
.
async
{
let
asset
=
AVAsset
(
url
:
url
)
let
imageGenerator
=
AVAssetImageGenerator
(
asset
:
asset
)
imageGenerator
.
appliesPreferredTrackTransform
=
true
imageGenerator
.
maximumSize
=
CGSize
(
width
:
300
,
height
:
300
)
let
time
=
CMTime
(
seconds
:
0.0
,
preferredTimescale
:
600
)
do
{
let
cgImage
=
try
imageGenerator
.
copyCGImage
(
at
:
time
,
actualTime
:
nil
)
let
thumbnail
=
UIImage
(
cgImage
:
cgImage
)
completion
(
thumbnail
)
}
catch
{
print
(
"生成视频缩略图失败:
\(
error
)
"
)
completion
(
nil
)
}
}
}
private
func
getVideoDuration
(
from
url
:
URL
,
completion
:
@escaping
(
TimeInterval
)
->
Void
)
{
DispatchQueue
.
global
(
qos
:
.
userInitiated
)
.
async
{
let
asset
=
AVAsset
(
url
:
url
)
let
duration
=
CMTimeGetSeconds
(
asset
.
duration
)
completion
(
duration
.
isNaN
?
0
:
duration
)
}
}
private
func
formatDuration
(
_
duration
:
TimeInterval
)
->
String
{
let
totalSeconds
=
Int
(
duration
)
let
minutes
=
totalSeconds
/
60
let
seconds
=
totalSeconds
%
60
if
minutes
>
0
{
return
String
(
format
:
"%d:%02d"
,
minutes
,
seconds
)
}
else
{
return
String
(
format
:
"0:%02d"
,
seconds
)
}
}
// MARK: - Actions
@objc
private
func
deleteButtonTapped
()
{
deleteCallback
?()
}
}
galaxy/galaxy/Classes/Modules/Community(社区)/Circle(下属社区)/V/YHCirclePhotoCell.swift
0 → 100644
View file @
e0ed7074
//
// YHCirclePhotoCell.swift
// galaxy
//
// Created by alexzzw on 2025/9/26.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import
UIKit
// MARK: - Custom Cells
class
YHCirclePhotoCell
:
UICollectionViewCell
{
var
deleteCallback
:
(()
->
Void
)?
private
lazy
var
imageView
:
UIImageView
=
{
let
imageView
=
UIImageView
()
imageView
.
contentMode
=
.
scaleAspectFill
imageView
.
clipsToBounds
=
true
return
imageView
}()
private
lazy
var
deleteButton
:
UIButton
=
{
let
button
=
UIButton
(
type
:
.
custom
)
button
.
setImage
(
UIImage
(
systemName
:
"xmark.circle.fill"
),
for
:
.
normal
)
button
.
tintColor
=
.
white
button
.
backgroundColor
=
UIColor
.
black
.
withAlphaComponent
(
0.5
)
button
.
layer
.
cornerRadius
=
10
button
.
addTarget
(
self
,
action
:
#selector(
deleteButtonTapped
)
,
for
:
.
touchUpInside
)
button
.
isHidden
=
true
return
button
}()
override
init
(
frame
:
CGRect
)
{
super
.
init
(
frame
:
frame
)
setupUI
()
}
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
private
func
setupUI
()
{
contentView
.
addSubview
(
imageView
)
contentView
.
addSubview
(
deleteButton
)
imageView
.
snp
.
makeConstraints
{
make
in
make
.
edges
.
equalToSuperview
()
}
deleteButton
.
snp
.
makeConstraints
{
make
in
make
.
top
.
right
.
equalToSuperview
()
.
inset
(
4
)
make
.
width
.
height
.
equalTo
(
20
)
}
}
func
configure
(
with
image
:
UIImage
)
{
imageView
.
image
=
image
}
@objc
private
func
deleteButtonTapped
()
{
deleteCallback
?()
}
}
galaxy/galaxy/Classes/Modules/Community(社区)/Circle(下属社区)/V/YHMediaUploadSheetView.swift
0 → 100644
View file @
e0ed7074
//
// YHMediaUploadSheetView.swift
// galaxy
//
// Created by alexzzw on 2025/9/27.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
/*
【Usage】
YHMediaUploadSheetView.sheetView().show { [weak self] mediaItems in
guard let self = self else { return }
print("获得 \(mediaItems.count) 个媒体文件")
// 处理选中的媒体文件
}
*/
import
UIKit
import
Photos
import
PhotosUI
import
AVFoundation
import
MobileCoreServices
import
SnapKit
enum
YHMediaUploadType
:
Int
{
case
camera
=
1
// 拍照/录像
case
photoLibrary
=
2
// 相册选择
case
cancel
=
3
// 取消
}
class
YHMediaUploadItem
{
var
type
:
YHMediaUploadType
var
title
:
String
init
(
type
:
YHMediaUploadType
,
title
:
String
)
{
self
.
type
=
type
self
.
title
=
title
}
}
class
YHMediaUploadSheetView
:
UIView
{
// 决定最终媒体选择数量最大值
var
maxSelectCount
:
Int
=
9
var
uploadTypeArr
=
[
YHMediaUploadItem
(
type
:
.
camera
,
title
:
"拍照"
),
YHMediaUploadItem
(
type
:
.
photoLibrary
,
title
:
"从手机相册选择"
),
YHMediaUploadItem
(
type
:
.
cancel
,
title
:
"取消"
)
]
// 上传媒体回调
var
uploadMediaBlock
:
(([
YHSelectMediaItem
])
->
Void
)?
private
var
needSelectVideo
=
false
lazy
var
blackMaskView
:
UIView
=
{
let
view
=
UIView
()
view
.
backgroundColor
=
UIColor
(
hex
:
0x0F1214
,
alpha
:
0.5
)
let
tap
=
UITapGestureRecognizer
(
target
:
self
,
action
:
#selector(
dismiss
)
)
view
.
addGestureRecognizer
(
tap
)
return
view
}()
lazy
var
whiteContentView
:
UIView
=
{
let
view
=
UIView
()
view
.
backgroundColor
=
.
white
return
view
}()
lazy
var
tableView
:
UITableView
=
{
let
tableView
=
UITableView
(
frame
:
.
zero
,
style
:
.
grouped
)
if
#available(iOS 11.0, *)
{
tableView
.
contentInsetAdjustmentBehavior
=
.
never
}
tableView
.
showsVerticalScrollIndicator
=
false
tableView
.
separatorStyle
=
.
none
tableView
.
delegate
=
self
tableView
.
dataSource
=
self
tableView
.
backgroundColor
=
.
white
tableView
.
isScrollEnabled
=
false
tableView
.
register
(
YHMediaUploadTypeCell
.
self
,
forCellReuseIdentifier
:
YHMediaUploadTypeCell
.
cellReuseIdentifier
)
return
tableView
}()
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
override
init
(
frame
:
CGRect
)
{
super
.
init
(
frame
:
frame
)
createUI
()
}
static
func
sheetView
()
->
YHMediaUploadSheetView
{
let
view
=
YHMediaUploadSheetView
(
frame
:
UIScreen
.
main
.
bounds
)
return
view
}
func
createUI
()
{
self
.
addSubview
(
blackMaskView
)
self
.
addSubview
(
whiteContentView
)
whiteContentView
.
addSubview
(
tableView
)
blackMaskView
.
snp
.
makeConstraints
{
make
in
make
.
edges
.
equalToSuperview
()
}
// 计算内容高度:2个section,第一个section 2行,第二个section 1行,加上section间距和安全区域
let
contentHeight
=
54.0
*
3
+
8.0
+
k_Height_safeAreaInsetsBottom
()
whiteContentView
.
snp
.
makeConstraints
{
make
in
make
.
left
.
right
.
bottom
.
equalToSuperview
()
make
.
height
.
equalTo
(
contentHeight
)
}
tableView
.
snp
.
makeConstraints
{
make
in
make
.
edges
.
equalToSuperview
()
}
}
}
extension
YHMediaUploadSheetView
{
func
show
(
needSelectVideo
:
Bool
=
false
,
completion
:
@escaping
([
YHSelectMediaItem
])
->
Void
)
{
self
.
needSelectVideo
=
needSelectVideo
self
.
uploadMediaBlock
=
completion
UIApplication
.
shared
.
yhKeyWindow
()?
.
addSubview
(
self
)
}
@objc
func
dismiss
()
{
self
.
removeFromSuperview
()
}
}
// MARK: - TableView DataSource & Delegate
extension
YHMediaUploadSheetView
:
UITableViewDelegate
,
UITableViewDataSource
{
func
numberOfSections
(
in
tableView
:
UITableView
)
->
Int
{
return
2
}
func
tableView
(
_
tableView
:
UITableView
,
numberOfRowsInSection
section
:
Int
)
->
Int
{
if
section
==
0
{
return
2
// 拍照 + 从手机相册选择
}
else
{
return
1
// 取消
}
}
func
tableView
(
_
tableView
:
UITableView
,
cellForRowAt
indexPath
:
IndexPath
)
->
UITableViewCell
{
guard
let
cell
=
tableView
.
dequeueReusableCell
(
withIdentifier
:
YHMediaUploadTypeCell
.
cellReuseIdentifier
,
for
:
indexPath
)
as?
YHMediaUploadTypeCell
else
{
return
UITableViewCell
()
}
var
itemIndex
=
0
if
indexPath
.
section
==
0
{
itemIndex
=
indexPath
.
row
}
else
{
itemIndex
=
2
// 取消按钮
}
if
itemIndex
<
uploadTypeArr
.
count
{
cell
.
item
=
uploadTypeArr
[
itemIndex
]
// 第一个section的第一行显示分割线
if
indexPath
.
section
==
0
&&
indexPath
.
row
==
0
{
cell
.
showSeparator
=
true
}
else
{
cell
.
showSeparator
=
false
}
}
return
cell
}
func
tableView
(
_
tableView
:
UITableView
,
didSelectRowAt
indexPath
:
IndexPath
)
{
tableView
.
deselectRow
(
at
:
indexPath
,
animated
:
true
)
var
itemIndex
=
0
if
indexPath
.
section
==
0
{
itemIndex
=
indexPath
.
row
}
else
{
itemIndex
=
2
// 取消按钮
}
if
itemIndex
<
uploadTypeArr
.
count
{
let
operationItem
=
uploadTypeArr
[
itemIndex
]
if
operationItem
.
type
==
.
cancel
{
dismiss
()
}
else
if
operationItem
.
type
==
.
photoLibrary
{
selectMediaFromPhotoLibrary
()
}
else
if
operationItem
.
type
==
.
camera
{
showCameraWithBothModes
()
}
}
}
func
tableView
(
_
tableView
:
UITableView
,
heightForRowAt
indexPath
:
IndexPath
)
->
CGFloat
{
return
54.0
}
func
tableView
(
_
tableView
:
UITableView
,
heightForHeaderInSection
section
:
Int
)
->
CGFloat
{
return
section
==
0
?
0.01
:
8.0
}
func
tableView
(
_
tableView
:
UITableView
,
heightForFooterInSection
section
:
Int
)
->
CGFloat
{
return
0.01
}
func
tableView
(
_
tableView
:
UITableView
,
viewForHeaderInSection
section
:
Int
)
->
UIView
?
{
if
section
==
0
{
let
view
=
UIView
()
view
.
backgroundColor
=
nil
return
view
}
else
{
let
view
=
UIView
()
view
.
backgroundColor
=
UIColor
.
brandGrayColor3
return
view
}
}
func
tableView
(
_
tableView
:
UITableView
,
viewForFooterInSection
section
:
Int
)
->
UIView
?
{
return
UIView
()
}
}
// MARK: - Camera & Photo Library
extension
YHMediaUploadSheetView
{
// 显示相机(拍照和录像在同一界面)
func
showCameraWithBothModes
()
{
checkCameraPermission
{
[
weak
self
]
in
self
?
.
presentCameraWithBothModes
()
}
}
// 从相册选择
func
selectMediaFromPhotoLibrary
()
{
checkPhotoLibraryPermission
{
[
weak
self
]
in
self
?
.
presentPhotoLibraryPicker
()
}
}
// 展示相机(同时支持拍照和录像)
func
presentCameraWithBothModes
()
{
guard
UIImagePickerController
.
isSourceTypeAvailable
(
.
camera
)
else
{
YHHUD
.
flash
(
message
:
"设备不支持相机功能"
)
return
}
let
picker
=
UIImagePickerController
()
picker
.
delegate
=
self
picker
.
sourceType
=
.
camera
// 同时支持拍照和录像
var
mediaTypes
:
[
String
]
=
[]
// 检查是否支持拍照和录像
if
let
availableTypes
=
UIImagePickerController
.
availableMediaTypes
(
for
:
.
camera
)
{
if
availableTypes
.
contains
(
kUTTypeImage
as
String
)
{
mediaTypes
.
append
(
kUTTypeImage
as
String
)
}
if
needSelectVideo
,
availableTypes
.
contains
(
kUTTypeMovie
as
String
)
{
mediaTypes
.
append
(
kUTTypeMovie
as
String
)
}
}
guard
!
mediaTypes
.
isEmpty
else
{
YHHUD
.
flash
(
message
:
"设备不支持拍照或录像功能"
)
return
}
picker
.
mediaTypes
=
mediaTypes
// 设置视频录制参数
picker
.
videoQuality
=
.
typeMedium
picker
.
videoMaximumDuration
=
60.0
// 60秒
// 默认是拍照模式,用户可以在界面中切换到录像
picker
.
cameraCaptureMode
=
.
photo
picker
.
allowsEditing
=
true
UIViewController
.
current
?
.
present
(
picker
,
animated
:
true
)
}
// 展示相册选择器
func
presentPhotoLibraryPicker
()
{
if
#available(iOS 14.0, *)
{
// iOS 14+ 使用 PHPickerViewController,支持多选
var
configuration
=
PHPickerConfiguration
()
configuration
.
selectionLimit
=
maxSelectCount
// 同时支持图片和视频
configuration
.
filter
=
needSelectVideo
?
.
any
(
of
:
[
.
images
,
.
videos
])
:
.
any
(
of
:
[
.
images
])
let
picker
=
PHPickerViewController
(
configuration
:
configuration
)
picker
.
delegate
=
self
UIViewController
.
current
?
.
present
(
picker
,
animated
:
true
)
}
else
{
// iOS 14以下使用 UIImagePickerController,只能单选
if
UIImagePickerController
.
isSourceTypeAvailable
(
.
photoLibrary
)
{
let
imagePicker
=
UIImagePickerController
()
imagePicker
.
delegate
=
self
imagePicker
.
sourceType
=
.
photoLibrary
imagePicker
.
mediaTypes
=
needSelectVideo
?
[
kUTTypeImage
as
String
,
kUTTypeMovie
as
String
]
:
[
kUTTypeImage
as
String
]
UIViewController
.
current
?
.
present
(
imagePicker
,
animated
:
true
)
}
}
}
}
// MARK: - 权限检查
extension
YHMediaUploadSheetView
{
func
checkCameraPermission
(
completion
:
@escaping
()
->
Void
)
{
let
cameraAuthStatus
=
AVCaptureDevice
.
authorizationStatus
(
for
:
.
video
)
switch
cameraAuthStatus
{
case
.
authorized
:
completion
()
case
.
notDetermined
:
AVCaptureDevice
.
requestAccess
(
for
:
.
video
)
{
granted
in
DispatchQueue
.
main
.
async
{
if
granted
{
completion
()
}
else
{
YHHUD
.
flash
(
message
:
"需要相机权限才能拍照"
)
}
}
}
case
.
denied
,
.
restricted
:
YHHUD
.
flash
(
message
:
"请在设置中打开相机权限"
)
@unknown
default
:
YHHUD
.
flash
(
message
:
"无法获取相机权限"
)
}
}
func
checkPhotoLibraryPermission
(
completion
:
@escaping
()
->
Void
)
{
let
photoAuthStatus
=
PHPhotoLibrary
.
authorizationStatus
()
switch
photoAuthStatus
{
case
.
authorized
,
.
limited
:
completion
()
case
.
notDetermined
:
PHPhotoLibrary
.
requestAuthorization
{
status
in
DispatchQueue
.
main
.
async
{
if
status
==
.
authorized
||
status
==
.
limited
{
completion
()
}
else
{
YHHUD
.
flash
(
message
:
"需要相册权限才能选择照片"
)
}
}
}
case
.
denied
,
.
restricted
:
YHHUD
.
flash
(
message
:
"请在设置中打开相册权限"
)
@unknown
default
:
YHHUD
.
flash
(
message
:
"无法获取相册权限"
)
}
}
}
// MARK: - UIImagePickerControllerDelegate
extension
YHMediaUploadSheetView
:
UIImagePickerControllerDelegate
,
UINavigationControllerDelegate
{
func
imagePickerController
(
_
picker
:
UIImagePickerController
,
didFinishPickingMediaWithInfo
info
:
[
UIImagePickerController
.
InfoKey
:
Any
])
{
guard
let
mediaType
=
info
[
.
mediaType
]
as?
String
else
{
picker
.
dismiss
(
animated
:
true
)
return
}
var
mediaItems
:
[
YHSelectMediaItem
]
=
[]
if
mediaType
==
kUTTypeImage
as
String
{
// 处理图片
if
let
image
=
info
[
.
originalImage
]
as?
UIImage
{
var
imageName
=
""
if
let
imageUrl
=
info
[
.
imageURL
]
as?
URL
{
imageName
=
imageUrl
.
lastPathComponent
}
if
imageName
.
isEmpty
{
let
timestamp
=
Date
()
.
timeIntervalSince1970
imageName
=
"
\(
timestamp
)
.jpg"
.
replacingOccurrences
(
of
:
"."
,
with
:
""
)
}
let
item
=
YHSelectMediaItem
(
name
:
imageName
,
type
:
.
image
,
image
:
image
)
mediaItems
.
append
(
item
)
}
}
else
if
mediaType
==
kUTTypeMovie
as
String
{
// 处理视频
if
let
videoURL
=
info
[
.
mediaURL
]
as?
URL
{
let
videoName
=
videoURL
.
lastPathComponent
let
item
=
YHSelectMediaItem
(
name
:
videoName
,
type
:
.
video
,
videoURL
:
videoURL
)
mediaItems
.
append
(
item
)
}
}
picker
.
dismiss
(
animated
:
true
)
{
self
.
uploadMediaBlock
?(
mediaItems
)
self
.
dismiss
()
}
}
func
imagePickerControllerDidCancel
(
_
picker
:
UIImagePickerController
)
{
picker
.
dismiss
(
animated
:
true
)
}
}
// MARK: - PHPickerViewControllerDelegate
@available(iOS 14.0, *)
extension
YHMediaUploadSheetView
:
PHPickerViewControllerDelegate
{
func
picker
(
_
picker
:
PHPickerViewController
,
didFinishPicking
results
:
[
PHPickerResult
])
{
picker
.
dismiss
(
animated
:
true
)
self
.
dismiss
()
if
results
.
count
<=
0
{
return
}
let
group
=
DispatchGroup
()
// 使用数组保持顺序,用可选类型处理失败情况
var
mediaItems
:
[
YHSelectMediaItem
?]
=
Array
(
repeating
:
nil
,
count
:
results
.
count
)
YHHUD
.
show
(
.
progress
(
message
:
"处理中..."
))
for
(
index
,
result
)
in
results
.
enumerated
()
{
group
.
enter
()
// 检查是否是图片
if
result
.
itemProvider
.
hasItemConformingToTypeIdentifier
(
UTType
.
image
.
identifier
)
{
result
.
itemProvider
.
loadFileRepresentation
(
forTypeIdentifier
:
UTType
.
image
.
identifier
)
{
url
,
_
in
defer
{
group
.
leave
()
}
if
let
url
=
url
,
let
imageData
=
try
?
Data
(
contentsOf
:
url
),
let
image
=
UIImage
(
data
:
imageData
)
{
let
imageName
=
url
.
lastPathComponent
.
isEmpty
?
"
\(
Date
()
.
timeIntervalSince1970
)
.jpg"
:
url
.
lastPathComponent
let
item
=
YHSelectMediaItem
(
name
:
imageName
,
type
:
.
image
,
image
:
image
)
mediaItems
[
index
]
=
item
}
}
}
// 检查是否是视频
else
if
result
.
itemProvider
.
hasItemConformingToTypeIdentifier
(
UTType
.
movie
.
identifier
)
{
result
.
itemProvider
.
loadFileRepresentation
(
forTypeIdentifier
:
UTType
.
movie
.
identifier
)
{
url
,
error
in
defer
{
group
.
leave
()
}
if
let
url
=
url
{
// 复制视频文件到临时目录
let
tempURL
=
URL
(
fileURLWithPath
:
NSTemporaryDirectory
())
.
appendingPathComponent
(
url
.
lastPathComponent
)
do
{
if
FileManager
.
default
.
fileExists
(
atPath
:
tempURL
.
path
)
{
try
FileManager
.
default
.
removeItem
(
at
:
tempURL
)
}
try
FileManager
.
default
.
copyItem
(
at
:
url
,
to
:
tempURL
)
let
videoName
=
tempURL
.
lastPathComponent
let
item
=
YHSelectMediaItem
(
name
:
videoName
,
type
:
.
video
,
videoURL
:
tempURL
)
mediaItems
[
index
]
=
item
}
catch
{
printLog
(
"视频文件复制失败:
\(
error
)
"
)
// 失败时 mediaItems[index] 保持为 nil
}
}
}
}
else
{
// 不支持的类型,直接 leave
group
.
leave
()
}
}
// 等待所有任务完成
group
.
notify
(
queue
:
.
main
)
{
YHHUD
.
hide
()
// 过滤掉失败的项目,保持成功项目的相对顺序
let
successfulItems
=
mediaItems
.
compactMap
{
$0
}
self
.
uploadMediaBlock
?(
successfulItems
)
}
}
}
// MARK: - YHMediaUploadTypeCell
class
YHMediaUploadTypeCell
:
UITableViewCell
{
static
let
cellReuseIdentifier
=
"YHMediaUploadTypeCell"
var
showSeparator
:
Bool
=
false
{
didSet
{
separatorView
.
isHidden
=
!
showSeparator
}
}
var
item
:
YHMediaUploadItem
?
{
didSet
{
guard
let
item
=
item
else
{
return
}
titleLabel
.
text
=
item
.
title
// 根据类型设置样式
if
item
.
type
==
.
cancel
{
titleLabel
.
font
=
UIFont
.
PFSC_R
(
ofSize
:
15
)
}
else
{
titleLabel
.
font
=
UIFont
.
PFSC_M
(
ofSize
:
15
)
}
}
}
lazy
var
titleLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
font
=
UIFont
.
PFSC_M
(
ofSize
:
15
)
label
.
textColor
=
UIColor
.
brandGrayColor8
label
.
textAlignment
=
.
center
return
label
}()
lazy
var
separatorView
:
UIView
=
{
let
view
=
UIView
()
view
.
backgroundColor
=
UIColor
.
separator
view
.
isHidden
=
true
return
view
}()
override
init
(
style
:
UITableViewCell
.
CellStyle
,
reuseIdentifier
:
String
?)
{
super
.
init
(
style
:
style
,
reuseIdentifier
:
reuseIdentifier
)
setupUI
()
}
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
func
setupUI
()
{
selectionStyle
=
.
none
backgroundColor
=
.
white
contentView
.
addSubview
(
titleLabel
)
contentView
.
addSubview
(
separatorView
)
titleLabel
.
snp
.
makeConstraints
{
make
in
make
.
center
.
equalToSuperview
()
}
separatorView
.
snp
.
makeConstraints
{
make
in
make
.
left
.
equalToSuperview
()
make
.
right
.
equalToSuperview
()
make
.
bottom
.
equalToSuperview
()
make
.
height
.
equalTo
(
0.5
)
}
}
}
galaxy/galaxy/Classes/Modules/IntelligentService(服务中心)/QMAS(优才)/MyCertificates(我的证书)/V/YHCertificateUploadSheetView.swift
View file @
e0ed7074
...
...
@@ -405,8 +405,31 @@ extension YHCertificateUploadSheetView: (UIImagePickerControllerDelegate & UINav
return
false
}
private
func
getCaptureDeviceAuthorization
(
notDeterminedBlock
:
(()
->
Void
)?)
->
Bool
?
{
let
cameraAuthStatus
=
AVCaptureDevice
.
authorizationStatus
(
for
:
.
video
)
switch
cameraAuthStatus
{
case
.
authorized
:
// 已授权,直接使用相机
return
true
case
.
notDetermined
:
// 未请求过权限,主动请求
AVCaptureDevice
.
requestAccess
(
for
:
.
video
)
{
_
in
notDeterminedBlock
?()
}
return
nil
case
.
denied
,
.
restricted
:
// 权限被拒绝或受限
break
@unknown
default
:
break
}
return
false
}
func
takePhoto
()
{
guard
let
authorization
=
get
PhotoLibrary
Authorization
(
notDeterminedBlock
:
{
guard
let
authorization
=
get
CaptureDevice
Authorization
(
notDeterminedBlock
:
{
DispatchQueue
.
main
.
async
{
self
.
takePhoto
()
}
...
...
@@ -415,7 +438,7 @@ extension YHCertificateUploadSheetView: (UIImagePickerControllerDelegate & UINav
}
if
!
authorization
{
YHHUD
.
flash
(
message
:
"请在设置中打开相
册
权限"
)
YHHUD
.
flash
(
message
:
"请在设置中打开相
机
权限"
)
return
}
...
...
galaxy/galaxy/Classes/Modules/IntelligentService(服务中心)/QMAS(优才)/MyDocuments(我的文书)/V/YHDocumentUploadView.swift
View file @
e0ed7074
...
...
@@ -310,8 +310,31 @@ extension YHDocumentUploadView: (UIImagePickerControllerDelegate & UINavigationC
return
false
}
private
func
getCaptureDeviceAuthorization
(
notDeterminedBlock
:
(()
->
Void
)?)
->
Bool
?
{
let
cameraAuthStatus
=
AVCaptureDevice
.
authorizationStatus
(
for
:
.
video
)
switch
cameraAuthStatus
{
case
.
authorized
:
// 已授权,直接使用相机
return
true
case
.
notDetermined
:
// 未请求过权限,主动请求
AVCaptureDevice
.
requestAccess
(
for
:
.
video
)
{
_
in
notDeterminedBlock
?()
}
return
nil
case
.
denied
,
.
restricted
:
// 权限被拒绝或受限
break
@unknown
default
:
break
}
return
false
}
func
takePhoto
()
{
guard
let
authorization
=
get
PhotoLibrary
Authorization
(
notDeterminedBlock
:
{
guard
let
authorization
=
get
CaptureDevice
Authorization
(
notDeterminedBlock
:
{
DispatchQueue
.
main
.
async
{
self
.
takePhoto
()
}
...
...
@@ -320,7 +343,7 @@ extension YHDocumentUploadView: (UIImagePickerControllerDelegate & UINavigationC
}
if
!
authorization
{
YHHUD
.
flash
(
message
:
"请在设置中打开相
册
权限"
)
YHHUD
.
flash
(
message
:
"请在设置中打开相
机
权限"
)
return
}
...
...
galaxy/galaxy/Classes/Modules/PictureReview(图片预览)/YHMediaBrowserViewController.swift
0 → 100644
View file @
e0ed7074
//
// YHMediaBrowserViewController.swift
// galaxy
//
// Created by alexzzw on 2025/9/27.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import
Kingfisher
import
UIKit
import
JXPhotoBrowser
import
Photos
import
PhotosUI
class
YHMediaBrowserViewController
:
JXPhotoBrowser
{
var
deleteMediaBlock
:
((
Int
)
->
Void
)?
var
dismissBlock
:
(()
->
Void
)?
lazy
var
navBar
:
UIView
=
{
let
v
=
UIView
()
let
backBtn
=
UIButton
()
backBtn
.
setImage
(
UIImage
(
named
:
"nav_back_white"
),
for
:
.
normal
)
backBtn
.
addTarget
(
self
,
action
:
#selector(
didBackBtnClicked
)
,
for
:
.
touchUpInside
)
v
.
addSubview
(
backBtn
)
let
deleteBtn
=
UIButton
()
let
img
=
UIImage
(
named
:
"media_brower_delete"
)
let
templateImage
=
img
?
.
withRenderingMode
(
.
alwaysTemplate
)
deleteBtn
.
setImage
(
templateImage
,
for
:
.
normal
)
deleteBtn
.
imageView
?
.
tintColor
=
.
white
deleteBtn
.
addTarget
(
self
,
action
:
#selector(
didDeleteBtnClicked
)
,
for
:
.
touchUpInside
)
v
.
addSubview
(
deleteBtn
)
backBtn
.
snp
.
makeConstraints
{
make
in
make
.
width
.
height
.
equalTo
(
44
)
make
.
left
.
equalToSuperview
()
make
.
bottom
.
equalToSuperview
()
}
deleteBtn
.
snp
.
makeConstraints
{
make
in
make
.
width
.
height
.
equalTo
(
44
)
make
.
right
.
equalToSuperview
()
make
.
bottom
.
equalToSuperview
()
}
return
v
}()
@objc
func
didBackBtnClicked
()
{
dismiss
()
}
@objc
func
didDeleteBtnClicked
()
{
let
index
=
self
.
browserView
.
pageIndex
deleteMediaBlock
?(
index
)
}
override
func
viewDidLoad
()
{
super
.
viewDidLoad
()
view
.
addSubview
(
navBar
)
navBar
.
snp
.
makeConstraints
{
make
in
make
.
left
.
right
.
top
.
equalToSuperview
()
make
.
height
.
equalTo
(
k_Height_NavigationtBarAndStatuBar
)
}
}
deinit
{
printLog
(
"YHMediaBrowserViewController deinit"
)
}
override
func
dismiss
()
{
super
.
dismiss
()
dismissBlock
?()
}
}
galaxy/galaxy/Classes/Modules/PictureReview(图片预览)/YHPictureReviewManager.swift
View file @
e0ed7074
...
...
@@ -20,7 +20,7 @@ class YHPictureReviewManager: NSObject {
private
var
curIndex
:
Int
=
0
private
var
arrPics
:
[
String
]
=
[]
private
var
arrPreviewItems
:
[
YHPreviewMediaItem
]
=
[]
}
extension
YHPictureReviewManager
{
...
...
@@ -28,7 +28,7 @@ extension YHPictureReviewManager {
guard
curIndex
>
-
1
,
arrPicturs
.
count
>
0
else
{
return
}
self
.
curIndex
=
curIndex
self
.
arrPics
=
arrPicturs
arrPics
=
arrPicturs
let
browser
=
YHPictureBrowserViewController
()
browser
.
numberOfItems
=
{
...
...
@@ -44,7 +44,7 @@ extension YHPictureReviewManager {
let
browserCell
=
context
.
cell
as?
JXPhotoBrowserImageCell
browserCell
?
.
index
=
context
.
index
let
placeholder
=
UIImage
(
named
:
"global_default_image"
)
browserCell
?
.
imageView
.
sd_setImage
(
with
:
url
,
placeholderImage
:
placeholder
,
options
:
[],
completed
:
{
(
_
,
_
,
_
,
_
)
in
browserCell
?
.
imageView
.
sd_setImage
(
with
:
url
,
placeholderImage
:
placeholder
,
options
:
[],
completed
:
{
_
,
_
,
_
,
_
in
browserCell
?
.
setNeedsLayout
()
})
...
...
@@ -66,17 +66,240 @@ extension YHPictureReviewManager {
browser
.
pageIndicator
=
JXPhotoBrowserNumberPageIndicator
()
browser
.
pageIndex
=
self
.
curIndex
browser
.
show
()
}
}
extension
YHPictureReviewManager
{
private
func
longPress
(
cell
:
JXPhotoBrowserImageCell
)
{
let
index
=
cell
.
index
if
index
<
self
.
arrPics
.
count
,
index
>
-
1
{
if
index
<
arrPics
.
count
,
index
>
-
1
{
let
view
=
YHLongtapPictureSheetView
.
sheetView
()
view
.
myUrl
=
arrPics
[
index
]
view
.
show
()
}
}
private
func
longPressMedia
(
cell
:
JXPhotoBrowserImageCell
)
{
let
index
=
cell
.
index
if
index
<
arrPreviewItems
.
count
,
index
>
-
1
{
let
view
=
YHLongtapPictureSheetView
.
sheetView
()
view
.
myUrl
=
self
.
arrPics
[
index
]
let
item
=
arrPreviewItems
[
index
]
if
item
.
type
==
.
image
{
switch
item
.
source
{
case
let
.
remoteURL
(
url
):
view
.
myUrl
=
url
view
.
show
()
case
.
localImage
:
break
case
.
localVideoURL
:
break
}
}
}
}
}
// MARK: - 新的混合媒体预览方法
extension
YHPictureReviewManager
{
/// 显示本地媒体预览(从 YHSelectMediaItem 数组)
/// - Parameters:
/// - curIndex: 当前索引
/// - selectMediaItems: 本地媒体项数组
/// - deleteCallback: 删除回调 (deletedIndex) -> Void
func
showLocalMedia
(
curIndex
:
Int
,
selectMediaItems
:
[
YHSelectMediaItem
],
deleteCallback
:
((
_
deletedIndex
:
Int
)
->
Void
)?
=
nil
)
{
let
previewItems
=
selectMediaItems
.
map
{
YHPreviewMediaItem
(
from
:
$0
)
}
showPreviewMedia
(
curIndex
:
curIndex
,
previewItems
:
previewItems
,
deleteCallback
:
deleteCallback
)
}
/// 显示远程媒体预览(从URL字符串数组,自动检测类型)
/// - Parameters:
/// - curIndex: 当前索引
/// - urlStrings: URL字符串数组
/// - deleteCallback: 删除回调 (deletedIndex) -> Void
func
showRemoteMedia
(
curIndex
:
Int
,
urlStrings
:
[
String
],
deleteCallback
:
((
_
deletedIndex
:
Int
)
->
Void
)?
=
nil
)
{
let
previewItems
=
urlStrings
.
map
{
urlString
->
YHPreviewMediaItem
in
let
type
=
detectMediaType
(
from
:
urlString
)
return
YHPreviewMediaItem
(
remoteURL
:
urlString
,
type
:
type
)
}
showPreviewMedia
(
curIndex
:
curIndex
,
previewItems
:
previewItems
,
deleteCallback
:
deleteCallback
)
}
/// 显示混合媒体预览(手动指定每个项目)
/// - Parameters:
/// - curIndex: 当前索引
/// - previewItems: 预览媒体项数组
/// - deleteCallback: 删除回调 (deletedIndex) -> Void
func
showPreviewMedia
(
curIndex
:
Int
,
previewItems
:
[
YHPreviewMediaItem
],
deleteCallback
:
((
_
deletedIndex
:
Int
)
->
Void
)?
=
nil
)
{
guard
curIndex
>
-
1
,
previewItems
.
count
>
0
else
{
return
}
self
.
curIndex
=
curIndex
arrPreviewItems
=
previewItems
let
browser
=
YHMediaBrowserViewController
()
browser
.
numberOfItems
=
{
[
weak
self
]
in
guard
let
self
=
self
else
{
return
0
}
return
self
.
arrPreviewItems
.
count
}
// 根据媒体类型返回对应的Cell类
browser
.
cellClassAtIndex
=
{
[
weak
self
]
index
in
guard
let
self
=
self
else
{
return
JXPhotoBrowserImageCell
.
self
}
guard
index
<
self
.
arrPreviewItems
.
count
else
{
return
JXPhotoBrowserImageCell
.
self
}
let
previewItem
=
self
.
arrPreviewItems
[
index
]
return
previewItem
.
type
==
.
video
?
YHVideoCell
.
self
:
JXPhotoBrowserImageCell
.
self
}
browser
.
reloadCellAtIndex
=
{
[
weak
self
]
context
in
guard
let
self
=
self
else
{
return
}
if
context
.
index
>=
self
.
arrPreviewItems
.
count
{
return
}
let
previewItem
=
self
.
arrPreviewItems
[
context
.
index
]
if
previewItem
.
type
==
.
video
{
// 视频Cell
let
browserCell
=
context
.
cell
as?
YHVideoCell
switch
previewItem
.
source
{
case
let
.
remoteURL
(
urlString
):
if
let
url
=
URL
(
string
:
urlString
)
{
browserCell
?
.
loadVideo
(
from
:
url
)
}
case
let
.
localVideoURL
(
url
):
browserCell
?
.
loadVideo
(
from
:
url
)
case
.
localImage
:
break
// 视频不应该是本地图片
}
}
else
{
// 图片Cell
let
browserCell
=
context
.
cell
as?
JXPhotoBrowserImageCell
browserCell
?
.
index
=
context
.
index
let
placeholder
=
UIImage
(
named
:
"global_default_image"
)
switch
previewItem
.
source
{
case
let
.
remoteURL
(
urlString
):
if
let
url
=
URL
(
string
:
urlString
)
{
browserCell
?
.
imageView
.
sd_setImage
(
with
:
url
,
placeholderImage
:
placeholder
,
options
:
[],
completed
:
{
_
,
_
,
_
,
_
in
browserCell
?
.
setNeedsLayout
()
})
}
case
let
.
localImage
(
image
):
browserCell
?
.
imageView
.
image
=
image
browserCell
?
.
setNeedsLayout
()
case
.
localVideoURL
:
break
// 图片不应该是视频URL
}
// 添加长按事件(仅图片)
browserCell
?
.
longPressedAction
=
{
[
weak
self
]
cell
,
_
in
guard
let
self
=
self
else
{
return
}
self
.
longPressMedia
(
cell
:
cell
)
}
}
}
// 视频播放控制
browser
.
cellWillAppear
=
{
[
weak
self
]
cell
,
index
in
guard
let
self
=
self
else
{
return
}
if
index
<
self
.
arrPreviewItems
.
count
{
let
previewItem
=
self
.
arrPreviewItems
[
index
]
if
previewItem
.
type
==
.
video
{
(
cell
as?
YHVideoCell
)?
.
playVideo
()
}
}
}
browser
.
cellWillDisappear
=
{
[
weak
self
]
cell
,
index
in
guard
let
self
=
self
else
{
return
}
if
index
<
self
.
arrPreviewItems
.
count
{
let
previewItem
=
self
.
arrPreviewItems
[
index
]
if
previewItem
.
type
==
.
video
{
(
cell
as?
YHVideoCell
)?
.
stopVideo
()
}
}
}
// 完善的删除逻辑
browser
.
deleteMediaBlock
=
{
[
weak
self
,
weak
browser
]
index
in
guard
let
self
=
self
else
{
return
}
// 删除数据
self
.
arrPreviewItems
.
remove
(
at
:
index
)
deleteCallback
?(
index
)
// 重新显示浏览器
if
!
self
.
arrPreviewItems
.
isEmpty
{
browser
?
.
dismiss
(
animated
:
false
)
{
[
weak
self
]
in
guard
let
self
=
self
else
{
return
}
let
newIndex
=
index
<
self
.
arrPreviewItems
.
count
?
index
:
self
.
arrPreviewItems
.
count
-
1
self
.
showPreviewMedia
(
curIndex
:
newIndex
,
previewItems
:
self
.
arrPreviewItems
,
deleteCallback
:
deleteCallback
)
}
}
else
{
browser
?
.
dismiss
(
animated
:
true
)
self
.
clearData
()
}
}
browser
.
dismissBlock
=
{
[
weak
self
]
in
guard
let
self
=
self
else
{
return
}
self
.
clearData
()
}
// 数字样式的页码指示器
browser
.
pageIndicator
=
JXPhotoBrowserNumberPageIndicator
()
browser
.
pageIndex
=
self
.
curIndex
browser
.
show
()
}
private
func
clearData
()
{
arrPreviewItems
.
removeAll
()
}
/// 根据URL检测媒体类型
/// - Parameter urlString: URL字符串
/// - Returns: 媒体类型
private
func
detectMediaType
(
from
urlString
:
String
)
->
YHMediaType
{
let
lowercasedUrl
=
urlString
.
lowercased
()
// 视频文件扩展名
let
videoExtensions
=
[
"mp4"
,
"mov"
,
"avi"
,
"mkv"
,
"wmv"
,
"flv"
,
"webm"
,
"m4v"
]
// 检查URL是否包含视频扩展名
for
ext
in
videoExtensions
{
if
lowercasedUrl
.
contains
(
".
\(
ext
)
"
)
{
return
.
video
}
}
// 默认为图片
return
.
image
}
}
galaxy/galaxy/Classes/Modules/PictureReview(图片预览)/YHPreviewMediaItem.swift
0 → 100644
View file @
e0ed7074
//
// YHPreviewMediaItem.swift
// galaxy
//
// Created by alexzzw on 2025/9/27.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import
UIKit
// 预览媒体项 - 专门用于预览的数据模型
struct
YHPreviewMediaItem
{
let
type
:
YHMediaType
let
source
:
YHMediaSource
enum
YHMediaSource
{
case
remoteURL
(
String
)
// 远程URL
case
localImage
(
UIImage
)
// 本地图片
case
localVideoURL
(
URL
)
// 本地视频文件URL
var
displayURL
:
String
{
switch
self
{
case
.
remoteURL
(
let
url
):
return
url
case
.
localImage
:
return
""
// 本地图片不需要URL
case
.
localVideoURL
(
let
url
):
return
url
.
absoluteString
}
}
}
// 从远程URL创建
init
(
remoteURL
:
String
,
type
:
YHMediaType
=
.
image
)
{
self
.
type
=
type
self
.
source
=
.
remoteURL
(
remoteURL
)
}
// 从本地图片创建
init
(
localImage
:
UIImage
)
{
self
.
type
=
.
image
self
.
source
=
.
localImage
(
localImage
)
}
// 从本地视频URL创建
init
(
localVideoURL
:
URL
)
{
self
.
type
=
.
video
self
.
source
=
.
localVideoURL
(
localVideoURL
)
}
// 从 YHSelectMediaItem 创建
init
(
from
selectMediaItem
:
YHSelectMediaItem
)
{
self
.
type
=
selectMediaItem
.
type
switch
selectMediaItem
.
type
{
case
.
image
:
if
let
image
=
selectMediaItem
.
image
{
self
.
source
=
.
localImage
(
image
)
}
else
{
// 如果没有本地图片,使用占位符
self
.
source
=
.
localImage
(
UIImage
(
named
:
"global_default_image"
)
??
UIImage
())
}
case
.
video
:
if
let
videoURL
=
selectMediaItem
.
videoURL
{
self
.
source
=
.
localVideoURL
(
videoURL
)
}
else
{
// 异常情况,创建一个空的远程URL
self
.
source
=
.
remoteURL
(
""
)
}
}
}
}
galaxy/galaxy/Classes/Modules/PictureReview(图片预览)/YHSelectMediaItem.swift
0 → 100644
View file @
e0ed7074
//
// YHSelectMediaItem.swift
// galaxy
//
// Created by alexzzw on 2025/9/27.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import
UIKit
class
YHSelectMediaItem
{
var
name
:
String
var
type
:
YHMediaType
var
image
:
UIImage
?
var
videoURL
:
URL
?
var
duration
:
TimeInterval
?
init
(
name
:
String
=
""
,
type
:
YHMediaType
=
.
image
,
image
:
UIImage
?
=
nil
,
videoURL
:
URL
?
=
nil
,
duration
:
TimeInterval
?
=
nil
)
{
self
.
name
=
name
self
.
type
=
type
self
.
image
=
image
self
.
videoURL
=
videoURL
self
.
duration
=
duration
}
}
enum
YHMediaType
{
case
image
case
video
}
galaxy/galaxy/Classes/Modules/PictureReview(图片预览)/YHVideoCell.swift
0 → 100644
View file @
e0ed7074
//
// YHVideoCell.swift
// galaxy
//
// Created by alexzzw on 2025/9/27.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import
Foundation
import
UIKit
import
AVFoundation
import
JXPhotoBrowser
import
SnapKit
class
YHVideoCell
:
UIView
,
JXPhotoBrowserCell
{
weak
var
photoBrowser
:
JXPhotoBrowser
?
lazy
var
player
=
AVPlayer
()
lazy
var
playerLayer
=
AVPlayerLayer
(
player
:
player
)
// 播放状态图标
lazy
var
playImageView
:
UIImageView
=
{
let
imageView
=
UIImageView
()
let
playImage
=
UIImage
(
named
:
"circle_play_icon"
)?
.
withRenderingMode
(
.
alwaysOriginal
)
imageView
.
image
=
playImage
return
imageView
}()
// 进度滑块
lazy
var
progressSlider
:
UISlider
=
{
let
slider
=
UISlider
()
slider
.
minimumTrackTintColor
=
.
white
slider
.
maximumTrackTintColor
=
UIColor
.
white
.
withAlphaComponent
(
0.3
)
slider
.
thumbTintColor
=
.
white
slider
.
addTarget
(
self
,
action
:
#selector(
sliderValueChanged
)
,
for
:
.
valueChanged
)
slider
.
addTarget
(
self
,
action
:
#selector(
sliderTouchDown
)
,
for
:
.
touchDown
)
slider
.
addTarget
(
self
,
action
:
#selector(
sliderTouchUp
)
,
for
:
.
touchUpInside
)
slider
.
addTarget
(
self
,
action
:
#selector(
sliderTouchUp
)
,
for
:
.
touchUpOutside
)
return
slider
}()
// 时间标签
lazy
var
timeLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
font
=
UIFont
.
systemFont
(
ofSize
:
12
)
label
.
textColor
=
.
white
label
.
text
=
"00:00 / 00:00"
label
.
textAlignment
=
.
right
return
label
}()
private
var
timeObserver
:
Any
?
private
var
isPlaying
=
false
private
var
isSeeking
=
false
static
func
generate
(
with
browser
:
JXPhotoBrowser
)
->
Self
{
let
instance
=
Self
.
init
(
frame
:
.
zero
)
instance
.
photoBrowser
=
browser
return
instance
}
required
override
init
(
frame
:
CGRect
)
{
super
.
init
(
frame
:
.
zero
)
setupUI
()
setupGestures
()
}
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
deinit
{
removeTimeObserver
()
NotificationCenter
.
default
.
removeObserver
(
self
)
}
private
func
setupUI
()
{
backgroundColor
=
.
black
layer
.
addSublayer
(
playerLayer
)
addSubview
(
playImageView
)
addSubview
(
progressSlider
)
addSubview
(
timeLabel
)
setupConstraints
()
setupNotifications
()
}
private
func
setupConstraints
()
{
playImageView
.
snp
.
makeConstraints
{
make
in
make
.
center
.
equalToSuperview
()
make
.
width
.
height
.
equalTo
(
48
)
}
progressSlider
.
snp
.
makeConstraints
{
make
in
make
.
left
.
right
.
equalToSuperview
()
.
inset
(
16
)
make
.
bottom
.
equalToSuperview
()
.
offset
(
-
60
)
make
.
height
.
equalTo
(
30
)
}
timeLabel
.
snp
.
makeConstraints
{
make
in
make
.
right
.
equalToSuperview
()
.
offset
(
-
16
)
make
.
top
.
equalTo
(
progressSlider
.
snp
.
bottom
)
.
offset
(
8
)
}
}
private
func
setupGestures
()
{
let
tap
=
UITapGestureRecognizer
(
target
:
self
,
action
:
#selector(
handleTap
)
)
addGestureRecognizer
(
tap
)
let
doubleTap
=
UITapGestureRecognizer
(
target
:
self
,
action
:
#selector(
handleDoubleTap
)
)
doubleTap
.
numberOfTapsRequired
=
2
addGestureRecognizer
(
doubleTap
)
tap
.
require
(
toFail
:
doubleTap
)
}
private
func
setupNotifications
()
{
NotificationCenter
.
default
.
addObserver
(
self
,
selector
:
#selector(
playerDidFinishPlaying
)
,
name
:
.
AVPlayerItemDidPlayToEndTime
,
object
:
nil
)
}
override
func
layoutSubviews
()
{
super
.
layoutSubviews
()
playerLayer
.
frame
=
bounds
}
// MARK: - Actions
@objc
private
func
handleTap
()
{
// 点击整个页面播放或暂停
if
isPlaying
{
pauseVideo
()
}
else
{
playVideo
()
}
// 显示/隐藏控制器
toggleControlsVisibility
()
}
@objc
private
func
handleDoubleTap
()
{
photoBrowser
?
.
dismiss
()
}
@objc
private
func
sliderValueChanged
(
_
sender
:
UISlider
)
{
guard
let
item
=
player
.
currentItem
else
{
return
}
let
duration
=
CMTimeGetSeconds
(
item
.
duration
)
if
duration
.
isFinite
&&
duration
>
0
{
let
targetTime
=
Double
(
sender
.
value
)
*
duration
let
currentTimeString
=
formatTime
(
targetTime
)
let
durationString
=
formatTime
(
duration
)
timeLabel
.
text
=
"
\(
currentTimeString
)
/
\(
durationString
)
"
}
}
@objc
private
func
sliderTouchDown
()
{
isSeeking
=
true
}
@objc
private
func
sliderTouchUp
()
{
guard
let
item
=
player
.
currentItem
else
{
return
}
let
duration
=
CMTimeGetSeconds
(
item
.
duration
)
if
duration
.
isFinite
&&
duration
>
0
{
let
targetTime
=
Double
(
progressSlider
.
value
)
*
duration
let
seekTime
=
CMTime
(
seconds
:
targetTime
,
preferredTimescale
:
CMTimeScale
(
NSEC_PER_SEC
))
player
.
seek
(
to
:
seekTime
)
{
[
weak
self
]
_
in
self
?
.
isSeeking
=
false
}
}
else
{
isSeeking
=
false
}
}
@objc
private
func
playerDidFinishPlaying
()
{
isPlaying
=
false
playImageView
.
isHidden
=
false
player
.
seek
(
to
:
CMTime
.
zero
)
progressSlider
.
value
=
0
updatePlayButtonImage
()
}
// MARK: - Video Control
func
loadVideo
(
from
url
:
URL
)
{
let
playerItem
=
AVPlayerItem
(
url
:
url
)
player
.
replaceCurrentItem
(
with
:
playerItem
)
addTimeObserver
()
updatePlayButtonImage
()
}
func
loadVideo
(
from
urlString
:
String
)
{
guard
let
url
=
URL
(
string
:
urlString
)
else
{
return
}
loadVideo
(
from
:
url
)
}
func
playVideo
()
{
player
.
play
()
isPlaying
=
true
playImageView
.
isHidden
=
true
updatePlayButtonImage
()
}
private
func
pauseVideo
()
{
player
.
pause
()
isPlaying
=
false
playImageView
.
isHidden
=
false
updatePlayButtonImage
()
}
func
stopVideo
()
{
player
.
pause
()
player
.
seek
(
to
:
CMTime
.
zero
)
isPlaying
=
false
playImageView
.
isHidden
=
false
progressSlider
.
value
=
0
updatePlayButtonImage
()
}
private
func
updatePlayButtonImage
()
{
let
imageName
=
isPlaying
?
"circle_pause_icon"
:
"circle_play_icon"
let
image
=
UIImage
(
named
:
imageName
)?
.
withRenderingMode
(
.
alwaysOriginal
)
playImageView
.
image
=
image
}
private
func
toggleControlsVisibility
()
{
let
isHidden
=
playImageView
.
alpha
==
0
UIView
.
animate
(
withDuration
:
0.3
)
{
self
.
playImageView
.
alpha
=
isHidden
?
1.0
:
0.0
self
.
progressSlider
.
alpha
=
isHidden
?
1.0
:
0.0
self
.
timeLabel
.
alpha
=
isHidden
?
1.0
:
0.0
}
completion
:
{
_
in
self
.
playImageView
.
isHidden
=
!
isHidden
&&
self
.
isPlaying
self
.
progressSlider
.
isHidden
=
!
isHidden
self
.
timeLabel
.
isHidden
=
!
isHidden
}
}
// MARK: - Time Observer
private
func
addTimeObserver
()
{
let
timeInterval
=
CMTime
(
seconds
:
0.1
,
preferredTimescale
:
CMTimeScale
(
NSEC_PER_SEC
))
timeObserver
=
player
.
addPeriodicTimeObserver
(
forInterval
:
timeInterval
,
queue
:
.
main
)
{
[
weak
self
]
_
in
self
?
.
updateProgress
()
}
}
private
func
removeTimeObserver
()
{
if
let
timeObserver
=
timeObserver
{
player
.
removeTimeObserver
(
timeObserver
)
self
.
timeObserver
=
nil
}
}
private
func
updateProgress
()
{
guard
let
item
=
player
.
currentItem
,
!
isSeeking
else
{
return
}
let
currentTime
=
CMTimeGetSeconds
(
player
.
currentTime
())
let
duration
=
CMTimeGetSeconds
(
item
.
duration
)
if
duration
.
isFinite
&&
duration
>
0
{
progressSlider
.
value
=
Float
(
currentTime
/
duration
)
let
currentTimeString
=
formatTime
(
currentTime
)
let
durationString
=
formatTime
(
duration
)
timeLabel
.
text
=
"
\(
currentTimeString
)
/
\(
durationString
)
"
}
}
private
func
formatTime
(
_
time
:
Double
)
->
String
{
if
time
.
isInfinite
||
time
.
isNaN
{
return
"00:00"
}
let
minutes
=
Int
(
time
)
/
60
let
seconds
=
Int
(
time
)
%
60
return
String
(
format
:
"%02d:%02d"
,
minutes
,
seconds
)
}
}
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_pause_icon.imageset/Contents.json
0 → 100644
View file @
e0ed7074
{
"images"
:
[
{
"idiom"
:
"universal"
,
"scale"
:
"1x"
},
{
"filename"
:
"circle_pause_icon@2x.png"
,
"idiom"
:
"universal"
,
"scale"
:
"2x"
},
{
"filename"
:
"circle_pause_icon@3x.png"
,
"idiom"
:
"universal"
,
"scale"
:
"3x"
}
],
"info"
:
{
"author"
:
"xcode"
,
"version"
:
1
}
}
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_pause_icon.imageset/circle_pause_icon@2x.png
0 → 100644
View file @
e0ed7074
1.39 KB
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_pause_icon.imageset/circle_pause_icon@3x.png
0 → 100644
View file @
e0ed7074
1.99 KB
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_play_icon.imageset/Contents.json
0 → 100644
View file @
e0ed7074
{
"images"
:
[
{
"idiom"
:
"universal"
,
"scale"
:
"1x"
},
{
"filename"
:
"circle_play_icon@2x.png"
,
"idiom"
:
"universal"
,
"scale"
:
"2x"
},
{
"filename"
:
"circle_play_icon@3x.png"
,
"idiom"
:
"universal"
,
"scale"
:
"3x"
}
],
"info"
:
{
"author"
:
"xcode"
,
"version"
:
1
}
}
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_play_icon.imageset/circle_play_icon@2x.png
0 → 100644
View file @
e0ed7074
1.67 KB
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_play_icon.imageset/circle_play_icon@3x.png
0 → 100644
View file @
e0ed7074
2.3 KB
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_plus_icon.imageset/Contents.json
0 → 100644
View file @
e0ed7074
{
"images"
:
[
{
"idiom"
:
"universal"
,
"scale"
:
"1x"
},
{
"filename"
:
"circle_plus_icon@2x.png"
,
"idiom"
:
"universal"
,
"scale"
:
"2x"
},
{
"filename"
:
"circle_plus_icon@3x.png"
,
"idiom"
:
"universal"
,
"scale"
:
"3x"
}
],
"info"
:
{
"author"
:
"xcode"
,
"version"
:
1
}
}
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_plus_icon.imageset/circle_plus_icon@2x.png
0 → 100644
View file @
e0ed7074
412 Bytes
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/circle_plus_icon.imageset/circle_plus_icon@3x.png
0 → 100644
View file @
e0ed7074
418 Bytes
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/media_brower_delete.imageset/Contents.json
0 → 100644
View file @
e0ed7074
{
"images"
:
[
{
"idiom"
:
"universal"
,
"scale"
:
"1x"
},
{
"filename"
:
"media_brower_delete@2x.png"
,
"idiom"
:
"universal"
,
"scale"
:
"2x"
},
{
"filename"
:
"media_brower_delete@3x.png"
,
"idiom"
:
"universal"
,
"scale"
:
"3x"
}
],
"info"
:
{
"author"
:
"xcode"
,
"version"
:
1
}
}
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/media_brower_delete.imageset/media_brower_delete@2x.png
0 → 100644
View file @
e0ed7074
573 Bytes
galaxy/galaxy/Res/Assets.xcassets/Community/Circle/media_brower_delete.imageset/media_brower_delete@3x.png
0 → 100644
View file @
e0ed7074
729 Bytes
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment