chat setting
22
Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_add_bg.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "chat_setting_add_bg@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "chat_setting_add_bg@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_add_bg.imageset/chat_setting_add_bg@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_add_bg.imageset/chat_setting_add_bg@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
22
Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_bg_delete.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "chat_setting_bg_delete@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "chat_setting_bg_delete@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_bg_delete.imageset/chat_setting_bg_delete@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 545 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_bg_delete.imageset/chat_setting_bg_delete@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
22
Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_delete.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "chat_setting_delete@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "chat_setting_delete@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_delete.imageset/chat_setting_delete@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 985 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/chat_setting_delete.imageset/chat_setting_delete@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_buttle.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_chat_buttle@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_chat_buttle@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_buttle.imageset/role_chat_buttle@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 608 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_buttle.imageset/role_chat_buttle@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_close.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_chat_close@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_chat_close@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_close.imageset/role_chat_close@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_close.imageset/role_chat_close@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_chat_mode@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_chat_mode@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_mode.imageset/role_chat_mode@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 805 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_mode.imageset/role_chat_mode@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_exchange_mode.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_exchange_mode@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_exchange_mode@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_exchange_mode.imageset/role_exchange_mode@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_exchange_mode.imageset/role_exchange_mode@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_font@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_font@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 256 B |
|
After Width: | Height: | Size: 404 B |
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_music@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_music@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_music.imageset/role_music@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 646 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_music.imageset/role_music@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_new_chat@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_new_chat@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_new_chat.imageset/role_new_chat@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_new_chat.imageset/role_new_chat@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 125 KiB |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_add.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_setting_add@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_setting_add@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_add.imageset/role_setting_add@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 531 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_add.imageset/role_setting_add@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1004 B |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_down.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_setting_down@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_setting_down@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_down.imageset/role_setting_down@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 364 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_down.imageset/role_setting_down@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 787 B |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_add.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_setting_font_add@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_setting_font_add@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_add.imageset/role_setting_font_add@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 649 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_add.imageset/role_setting_font_add@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_sub.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_setting_font_sub@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_setting_font_sub@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_sub.imageset/role_setting_font_sub@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 615 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_font_sub.imageset/role_setting_font_sub@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_go.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_setting_go@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_setting_go@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_go.imageset/role_setting_go@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 350 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_go.imageset/role_setting_go@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 743 B |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_sub.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_setting_sub@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_setting_sub@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_sub.imageset/role_setting_sub@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 524 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_setting_sub.imageset/role_setting_sub@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1015 B |
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_talk@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_talk@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 888 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_text_mode@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_text_mode@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_text_mode.imageset/role_text_mode@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 673 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_text_mode.imageset/role_text_mode@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_voice@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "role_voice@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_voice.imageset/role_voice@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_voice.imageset/role_voice@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
22
Visual_Novel_iOS/Assets.xcassets/Role/rolel_setting_selected.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "rolel_setting_selected@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "rolel_setting_selected@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Visual_Novel_iOS/Assets.xcassets/Role/rolel_setting_selected.imageset/rolel_setting_selected@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 656 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/rolel_setting_selected.imageset/rolel_setting_selected@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -395,9 +395,16 @@ extension SessionController {
|
|||
// sessionNavigationView.upDownNoticeView.showUnlocked(string: "XY")
|
||||
inputEntrance.inputTextView.resignFirstResponder()
|
||||
|
||||
let vc = ChatSettingListController()
|
||||
vc.aiId = aiId
|
||||
navigationController?.pushViewController(vc, animated: true)
|
||||
// let vc = ChatSettingListController()
|
||||
// vc.aiId = aiId
|
||||
// navigationController?.pushViewController(vc, animated: true)
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.swipeBgView.alpha = 1.0
|
||||
self.swipeView.snp.updateConstraints { make in
|
||||
make.left.equalToSuperview().offset(UIScreen.width * 0.16)
|
||||
}
|
||||
self.view.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func tapLikeButton(){
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ class SessionController: CLBaseViewController {
|
|||
var tableView: UITableView!
|
||||
// var headView: SessionAIHeadView!
|
||||
|
||||
var swipeView: ChatSettingSwipeView!
|
||||
var swipeBgView: UIView!
|
||||
|
||||
// MARK: BottomViews
|
||||
var bottomViewsStackV : InputStackView!
|
||||
var toolView: UIView!
|
||||
|
|
@ -197,6 +200,34 @@ extension SessionController {
|
|||
|
||||
view.bringSubviewToFront(sessionNavigationView)
|
||||
view.bringSubviewToFront(bottomViewsStackV)
|
||||
|
||||
swipeBgView = {
|
||||
let bgView = UIView()
|
||||
bgView.alpha = 0.0
|
||||
bgView.backgroundColor = UIColor.init(white: 0.0, alpha: 0.8)
|
||||
view.addSubview(bgView)
|
||||
bgView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(bgViewTap)))
|
||||
bgView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
return bgView
|
||||
}()
|
||||
|
||||
swipeView = {
|
||||
let v = ChatSettingSwipeView()
|
||||
v.closeAction = { [weak self] in
|
||||
self?.bgViewTap()
|
||||
}
|
||||
v.backgroundColor = .white
|
||||
view.addSubview(v)
|
||||
v.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().inset(UIScreen.width)
|
||||
make.top.bottom.equalToSuperview()
|
||||
make.width.equalTo(UIScreen.width * 0.84)
|
||||
}
|
||||
return v
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
func setupUserInfo() {
|
||||
|
|
@ -259,8 +290,15 @@ extension SessionController {
|
|||
markReadAll()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@objc func bgViewTap() {
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.swipeBgView.alpha = 0.0
|
||||
self.swipeView.snp.updateConstraints { make in
|
||||
make.left.equalToSuperview().inset(UIScreen.width)
|
||||
}
|
||||
self.view.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
//
|
||||
// ChatBackgroundCell.swift
|
||||
// Visual_Novel_iOS
|
||||
//
|
||||
// Created by mh on 2025/10/27.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SnapKit
|
||||
|
||||
struct BackgroundRow: RowModel {
|
||||
let count: Int
|
||||
var cellReuseID: String { "ChatBackgroundCell" }
|
||||
func cellHeight(tableWidth: CGFloat) -> CGFloat {
|
||||
// 使用 UITableView.automaticDimension 让 cell 自动计算高度
|
||||
return UITableView.automaticDimension
|
||||
}
|
||||
}
|
||||
|
||||
class ChatBackgroundCell: UITableViewCell, CellConfigurable {
|
||||
|
||||
private var collectionHeight: Constraint!
|
||||
private var itemCount: Int = 6 // 默认6个item
|
||||
private var layout: UICollectionViewFlowLayout!
|
||||
|
||||
lazy var collectionView: UICollectionView = {
|
||||
layout = UICollectionViewFlowLayout()
|
||||
layout.minimumLineSpacing = 10.0
|
||||
layout.minimumInteritemSpacing = 10.0
|
||||
layout.sectionInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
|
||||
|
||||
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||||
collectionView.delegate = self
|
||||
collectionView.dataSource = self
|
||||
collectionView.isScrollEnabled = false
|
||||
collectionView.backgroundColor = .clear
|
||||
collectionView.register(ChatBgCollectionCell.self, forCellWithReuseIdentifier: "ChatBgCollectionCell")
|
||||
return collectionView
|
||||
}()
|
||||
|
||||
func configure(with row: RowModel) {
|
||||
guard let row = row as? BackgroundRow else { return }
|
||||
|
||||
// 更新数据源
|
||||
self.itemCount = row.count
|
||||
|
||||
// 延迟更新,确保 collectionView 的 frame 已经确定
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.updateCollectionViewLayout()
|
||||
}
|
||||
}
|
||||
|
||||
private var lastCalculatedWidth: CGFloat = 0
|
||||
|
||||
private func updateCollectionViewLayout() {
|
||||
guard collectionView.frame.width > 0 else { return }
|
||||
|
||||
// 避免重复计算
|
||||
if abs(lastCalculatedWidth - collectionView.frame.width) < 1.0 {
|
||||
return
|
||||
}
|
||||
lastCalculatedWidth = collectionView.frame.width
|
||||
|
||||
// 重新加载数据,触发 sizeForItemAt 计算
|
||||
collectionView.reloadData()
|
||||
|
||||
// 强制布局更新
|
||||
collectionView.layoutIfNeeded()
|
||||
|
||||
// 获取准确的内容高度并更新约束
|
||||
let contentHeight = collectionView.collectionViewLayout.collectionViewContentSize.height
|
||||
if contentHeight > 0 {
|
||||
collectionHeight.update(offset: contentHeight)
|
||||
}
|
||||
}
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
configureViews()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func configureViews() {
|
||||
contentView.addSubview(collectionView)
|
||||
|
||||
collectionView.snp.makeConstraints { make in
|
||||
make.top.leading.trailing.equalToSuperview()
|
||||
make.width.equalToSuperview()
|
||||
make.bottom.equalToSuperview().priority(999) // 让底部约束优先级稍低
|
||||
collectionHeight = make.height.equalTo(100).priority(1000).constraint // 初始占位高度
|
||||
}
|
||||
}
|
||||
|
||||
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
|
||||
// 如果 collectionView 还没有正确的 frame,先设置一个临时的
|
||||
if collectionView.frame.width == 0 {
|
||||
collectionView.frame = CGRect(x: 0, y: 0, width: targetSize.width, height: 100)
|
||||
updateCollectionViewLayout()
|
||||
}
|
||||
|
||||
// 返回计算后的尺寸
|
||||
return super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ChatBackgroundCell: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return itemCount
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ChatBgCollectionCell", for: indexPath) as! ChatBgCollectionCell
|
||||
|
||||
// 这里可以根据 indexPath 配置不同的 cell 内容
|
||||
// 例如:cell.configure(with: data[indexPath.item])
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||||
let layout = collectionViewLayout as! UICollectionViewFlowLayout
|
||||
let availableWidth = collectionView.frame.width - layout.sectionInset.left - layout.sectionInset.right
|
||||
let itemWidth = (availableWidth - 2 * layout.minimumInteritemSpacing) / 3.0
|
||||
let itemHeight = itemWidth * (116.0 / 87)
|
||||
return CGSize(width: itemWidth, height: itemHeight)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
//
|
||||
// ChatBgCollectionCell.swift
|
||||
// Visual_Novel_iOS
|
||||
//
|
||||
// Created by mh on 2025/10/28.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class ChatBgCollectionCell: UICollectionViewCell {
|
||||
|
||||
lazy var containView: UIView = {
|
||||
let v = UIView()
|
||||
v.backgroundColor = UIColor(hex: "#F3F4FF")
|
||||
v.cornerRadius = 10.0
|
||||
v.clipsToBounds = true
|
||||
return v
|
||||
}()
|
||||
|
||||
lazy var contentImgView: UIImageView = {
|
||||
let imgView = UIImageView()
|
||||
return imgView
|
||||
}()
|
||||
|
||||
lazy var selectedImgView: UIImageView = {
|
||||
let imgview = UIImageView(image: UIImage(named: "rolel_setting_selected"))
|
||||
return imgview
|
||||
}()
|
||||
|
||||
lazy var deleteImgView: UIImageView = {
|
||||
let imgView = UIImageView(image: UIImage(named: "chat_setting_bg_delete"))
|
||||
return imgView
|
||||
}()
|
||||
|
||||
lazy var addImgView: UIImageView = {
|
||||
let imgView = UIImageView(image: UIImage(named: "chat_setting_add_bg"))
|
||||
return imgView
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupViews()
|
||||
setupData()
|
||||
setupEvent()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setupViews() {
|
||||
contentView.addSubview(containView)
|
||||
containView.addSubview(contentImgView)
|
||||
containView.addSubview(addImgView)
|
||||
containView.addSubview(selectedImgView)
|
||||
containView.addSubview(deleteImgView)
|
||||
|
||||
containView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
|
||||
contentImgView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(2)
|
||||
}
|
||||
|
||||
addImgView.snp.makeConstraints { make in
|
||||
make.centerX.centerY.equalToSuperview()
|
||||
}
|
||||
|
||||
selectedImgView.snp.makeConstraints { make in
|
||||
make.right.bottom.equalToSuperview().inset(0)
|
||||
}
|
||||
|
||||
deleteImgView.snp.makeConstraints { make in
|
||||
make.centerX.equalToSuperview()
|
||||
make.bottom.equalToSuperview().inset(7)
|
||||
}
|
||||
}
|
||||
|
||||
func setupData() {}
|
||||
|
||||
func setupEvent() {}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
//
|
||||
// ChatFontCell.swift
|
||||
// Visual_Novel_iOS
|
||||
//
|
||||
// Created by mh on 2025/10/27.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
// font
|
||||
struct FontRow: RowModel {
|
||||
let count: String
|
||||
let icon: String
|
||||
let title: String
|
||||
var cellReuseID: String { "ChatFontCell" }
|
||||
func cellHeight(tableWidth: CGFloat) -> CGFloat { 50 }
|
||||
}
|
||||
|
||||
class ChatFontCell: ChatSettingBaseCell, CellConfigurable {
|
||||
|
||||
lazy var iconImgView: UIImageView = {
|
||||
let imgView = UIImageView(image: UIImage(named: "role_exchange_mode"))
|
||||
return imgView
|
||||
}()
|
||||
|
||||
lazy var titleLab: UILabel = {
|
||||
let lab = UILabel()
|
||||
lab.text = "XL-0826-32K"
|
||||
lab.font = UIFont.boldSystemFont(ofSize: 14)
|
||||
lab.textColor = UIColor(hex: "#666666")
|
||||
return lab
|
||||
}()
|
||||
|
||||
lazy var fontSub: UIButton = {
|
||||
let btn = UIButton()
|
||||
btn.setImage(UIImage(named: "role_setting_font_sub"), for: .normal)
|
||||
btn.addTarget(self, action: #selector(fontSubTap), for: .touchUpInside)
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var fontAdd: UIButton = {
|
||||
let btn = UIButton()
|
||||
btn.setImage(UIImage(named: "role_setting_font_add"), for: .normal)
|
||||
btn.addTarget(self, action: #selector(fontAddTap), for: .touchUpInside)
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var fontLab: UILabel = {
|
||||
let lab = UILabel()
|
||||
lab.text = "20"
|
||||
lab.font = UIFont.systemFont(ofSize: 14)
|
||||
lab.textColor = UIColor(hex: "#999999")
|
||||
lab.textAlignment = .center
|
||||
return lab
|
||||
}()
|
||||
|
||||
func configure(with row: RowModel) {
|
||||
guard let row = row as? FontRow else { return }
|
||||
titleLab.text = row.title
|
||||
iconImgView.image = UIImage(named: row.icon)
|
||||
}
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
configureViews()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func configureViews() {
|
||||
|
||||
containerView.addSubview(iconImgView)
|
||||
containerView.addSubview(titleLab)
|
||||
containerView.addSubview(fontSub)
|
||||
containerView.addSubview(fontAdd)
|
||||
containerView.addSubview(fontLab)
|
||||
|
||||
iconImgView.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.left.equalToSuperview().offset(12)
|
||||
make.width.height.equalTo(21)
|
||||
}
|
||||
|
||||
titleLab.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.left.equalTo(iconImgView.snp.right).offset(9)
|
||||
make.right.equalTo(fontSub.snp.left).offset(-5)
|
||||
}
|
||||
|
||||
fontAdd.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.right.equalToSuperview().inset(20)
|
||||
make.width.height.equalTo(40)
|
||||
}
|
||||
|
||||
fontLab.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.right.equalTo(fontAdd.snp.left).offset(-10)
|
||||
}
|
||||
|
||||
fontSub.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.right.equalTo(fontLab.snp.left).offset(-10)
|
||||
make.width.height.equalTo(40)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func fontSubTap() {
|
||||
print("sub sub sub")
|
||||
}
|
||||
|
||||
@objc func fontAddTap() {
|
||||
print("add add add")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
//
|
||||
// ChatHistoryCell.swift
|
||||
// Visual_Novel_iOS
|
||||
//
|
||||
// Created by mh on 2025/10/27.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SnapKit
|
||||
|
||||
struct HistoryRow: RowModel {
|
||||
let time: String
|
||||
let icon: String
|
||||
let title: String
|
||||
let itemCount: Int // 添加数据项数量
|
||||
var cellReuseID: String { "ChatHistoryCell" }
|
||||
func cellHeight(tableWidth: CGFloat) -> CGFloat {
|
||||
// 使用更准确的高度计算
|
||||
let singleCellHeight = ChatHistoryContentCell.calculateHeight(for: tableWidth)
|
||||
let deleteButtonHeight: CGFloat = 62 // DELETE按钮高度(footer view)
|
||||
let totalHeight = CGFloat(itemCount) * singleCellHeight + deleteButtonHeight
|
||||
return totalHeight
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ChatHistoryCell: UITableViewCell, CellConfigurable {
|
||||
|
||||
private var tableViewHeight: Constraint!
|
||||
private var itemCount: Int = 3 // 默认3个item
|
||||
private var lastCalculatedHeight: CGFloat = 0
|
||||
private var isLayoutConfigured = false
|
||||
|
||||
lazy var tableView: UITableView = {
|
||||
let tableView = UITableView(frame: .zero, style: .plain)
|
||||
tableView.separatorStyle = .none
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
tableView.estimatedRowHeight = 60 // 更准确的预估高度
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.backgroundColor = .clear
|
||||
tableView.showsVerticalScrollIndicator = false
|
||||
tableView.register(ChatHistoryContentCell.self, forCellReuseIdentifier: "ChatHistoryContentCell")
|
||||
tableView.contentInset = .zero
|
||||
tableView.isScrollEnabled = false
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
return tableView
|
||||
}()
|
||||
|
||||
func configure(with row: RowModel) {
|
||||
guard let row = row as? HistoryRow else { return }
|
||||
|
||||
// 更新数据源
|
||||
self.itemCount = row.itemCount
|
||||
|
||||
// 立即更新tableView数据
|
||||
tableView.reloadData()
|
||||
|
||||
// 如果布局已经配置,立即更新高度
|
||||
if isLayoutConfigured {
|
||||
updateTableViewLayout()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateTableViewLayout() {
|
||||
// 确保tableView已经布局完成
|
||||
tableView.layoutIfNeeded()
|
||||
|
||||
// 强制计算contentSize
|
||||
tableView.setNeedsLayout()
|
||||
tableView.layoutIfNeeded()
|
||||
|
||||
// tableView.contentSize.height 已经包含了所有内容的高度,包括footer view
|
||||
let totalH = tableView.contentSize.height
|
||||
|
||||
// 避免死循环和无效更新
|
||||
guard abs(lastCalculatedHeight - totalH) > 1.0 else { return }
|
||||
lastCalculatedHeight = totalH
|
||||
|
||||
// 更新高度约束
|
||||
tableViewHeight.update(offset: totalH)
|
||||
|
||||
// 通知父tableView更新布局
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
// 通知父tableView需要更新高度
|
||||
if let tableView = self.superview as? UITableView {
|
||||
tableView.beginUpdates()
|
||||
tableView.endUpdates()
|
||||
} else {
|
||||
// 如果不是直接父tableView,则向上查找
|
||||
var currentView = self.superview
|
||||
while currentView != nil {
|
||||
if let tableView = currentView as? UITableView {
|
||||
tableView.beginUpdates()
|
||||
tableView.endUpdates()
|
||||
break
|
||||
}
|
||||
currentView = currentView?.superview
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
// 确保Cell本身宽度确定后再计算
|
||||
guard contentView.bounds.width > 0 else { return }
|
||||
|
||||
// 设置tableView的宽度
|
||||
tableView.frame.size.width = contentView.bounds.width
|
||||
|
||||
// 标记布局已配置
|
||||
if !isLayoutConfigured {
|
||||
isLayoutConfigured = true
|
||||
}
|
||||
|
||||
// 更新tableView布局
|
||||
updateTableViewLayout()
|
||||
}
|
||||
|
||||
|
||||
@objc func deleteBtnTap() {
|
||||
|
||||
}
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
configureViews()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func configureViews() {
|
||||
contentView.addSubview(tableView)
|
||||
|
||||
tableView.snp.makeConstraints { make in
|
||||
make.top.left.right.equalToSuperview()
|
||||
make.width.equalToSuperview()
|
||||
make.bottom.equalToSuperview().priority(.low) // 让底部约束优先级稍低
|
||||
tableViewHeight = make.height.equalTo(100).priority(1000).constraint // 初始占位高度
|
||||
}
|
||||
|
||||
// 确保tableView的约束正确设置
|
||||
tableView.setContentHuggingPriority(.required, for: .vertical)
|
||||
tableView.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ChatHistoryCell: UITableViewDelegate, UITableViewDataSource {
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return itemCount
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "ChatHistoryContentCell", for: indexPath) as! ChatHistoryContentCell
|
||||
cell.selectionStyle = .none
|
||||
|
||||
// 这里可以根据 indexPath 配置不同的 cell 内容
|
||||
// 例如:cell.configure(with: data[indexPath.item])
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
return CGFLOAT_MIN
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
|
||||
return UITableView.automaticDimension
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
||||
let footer = UIView()
|
||||
footer.backgroundColor = .clear
|
||||
|
||||
let deleteBtn = UIButton()
|
||||
deleteBtn.cornerRadius = 25.0
|
||||
deleteBtn.borderColor = UIColor(hex: "#FF3B30")
|
||||
deleteBtn.borderWidth = 2.0
|
||||
deleteBtn.setTitle("DELETE ", for: .normal)
|
||||
deleteBtn.setTitleColor(UIColor(hex: "#FF3B30"), for: .normal)
|
||||
deleteBtn.setImage(UIImage(named: "chat_setting_delete"), for: .normal)
|
||||
deleteBtn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 17.0)
|
||||
deleteBtn.setUp(.default, padding: 5.0)
|
||||
deleteBtn.addTarget(self, action: #selector(deleteBtnTap), for: .touchUpInside)
|
||||
footer.addSubview(deleteBtn)
|
||||
|
||||
deleteBtn.snp.makeConstraints { make in
|
||||
make.top.equalToSuperview().offset(10)
|
||||
make.bottom.equalToSuperview().inset(2)
|
||||
make.left.right.equalToSuperview().inset(20)
|
||||
make.height.equalTo(50)
|
||||
}
|
||||
|
||||
return footer
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
//
|
||||
// ChatHistoryContentCell.swift
|
||||
// Visual_Novel_iOS
|
||||
//
|
||||
// Created by mh on 2025/10/27.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class ChatHistoryContentCell: UITableViewCell {
|
||||
|
||||
lazy var containerView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = UIColor(hex: "#F6F6F6")
|
||||
view.cornerRadius = 15.0
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var iconImgView: UIImageView = {
|
||||
let imgView = UIImageView()
|
||||
imgView.cornerRadius = 12.5
|
||||
imgView.backgroundColor = .blue
|
||||
return imgView
|
||||
}()
|
||||
|
||||
lazy var timeLab: UILabel = {
|
||||
let lab = UILabel()
|
||||
lab.text = "2025/09/26 17:30"
|
||||
lab.textColor = UIColor.hexString("#999999", alpha: 0.6)
|
||||
lab.font = UIFont.systemFont(ofSize: 12)
|
||||
return lab
|
||||
}()
|
||||
|
||||
lazy var contentLab: UILabel = {
|
||||
let lab = UILabel()
|
||||
lab.text = "The Boss Fell for Me: My Days Screwing Nuts at Foxconn"
|
||||
lab.textColor = UIColor(hex: "#666666")
|
||||
lab.font = UIFont.systemFont(ofSize: 14)
|
||||
lab.numberOfLines = 2
|
||||
return lab
|
||||
}()
|
||||
|
||||
lazy var arrowImgView: UIImageView = {
|
||||
let imgView = UIImageView(image: UIImage(named: "role_setting_go"))
|
||||
return imgView
|
||||
}()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
configureViews()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func configureViews() {
|
||||
|
||||
contentView.addSubview(containerView)
|
||||
containerView.addSubview(iconImgView)
|
||||
containerView.addSubview(timeLab)
|
||||
containerView.addSubview(contentLab)
|
||||
containerView.addSubview(arrowImgView)
|
||||
|
||||
containerView.snp.makeConstraints { make in
|
||||
make.left.right.equalToSuperview().inset(20)
|
||||
make.top.bottom.equalToSuperview().inset(2.5)
|
||||
}
|
||||
|
||||
iconImgView.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().inset(12)
|
||||
make.top.equalToSuperview().offset(10)
|
||||
make.width.height.equalTo(25)
|
||||
}
|
||||
|
||||
timeLab.snp.makeConstraints { make in
|
||||
make.left.equalTo(iconImgView.snp.right).offset(8)
|
||||
make.top.equalTo(iconImgView.snp.top)
|
||||
make.right.equalTo(arrowImgView.snp.left).offset(-15)
|
||||
}
|
||||
|
||||
contentLab.snp.makeConstraints { make in
|
||||
make.left.equalTo(timeLab.snp.left)
|
||||
make.right.equalTo(timeLab.snp.right).offset(-15)
|
||||
make.top.equalTo(timeLab.snp.bottom).offset(6)
|
||||
make.bottom.equalToSuperview().inset(10)
|
||||
}
|
||||
|
||||
arrowImgView.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.right.equalToSuperview().inset(15)
|
||||
make.width.height.equalTo(15)
|
||||
}
|
||||
|
||||
// 设置内容优先级,确保自动布局正确计算高度
|
||||
contentLab.setContentHuggingPriority(.required, for: .vertical)
|
||||
contentLab.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||
}
|
||||
|
||||
// 计算cell的准确高度
|
||||
static func calculateHeight(for width: CGFloat) -> CGFloat {
|
||||
let containerWidth = width - 40 // 左右各20的inset
|
||||
let iconHeight: CGFloat = 25
|
||||
let iconTopMargin: CGFloat = 10
|
||||
let timeToContentMargin: CGFloat = 6
|
||||
let contentBottomMargin: CGFloat = 10
|
||||
let cellTopBottomMargin: CGFloat = 5 // 上下各2.5的inset
|
||||
|
||||
// 计算时间标签高度
|
||||
let timeHeight: CGFloat = 12 // 字体大小12
|
||||
|
||||
// 计算内容标签高度(2行文本)
|
||||
let contentFont = UIFont.systemFont(ofSize: 14)
|
||||
let contentWidth = containerWidth - 12 - 25 - 8 - 15 - 15 // 减去各种边距
|
||||
let contentHeight = contentFont.lineHeight * 2 // 2行文本
|
||||
|
||||
let totalHeight = cellTopBottomMargin + iconTopMargin + max(iconHeight, timeHeight) + timeToContentMargin + contentHeight + contentBottomMargin + cellTopBottomMargin
|
||||
|
||||
return totalHeight
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
//
|
||||
// ChatResponseTokenCell.swift
|
||||
// Visual_Novel_iOS
|
||||
//
|
||||
// Created by mh on 2025/10/27.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
// token
|
||||
struct TokenRow: RowModel {
|
||||
let count: String
|
||||
var cellReuseID: String { "ChatResponseTokenCell" }
|
||||
func cellHeight(tableWidth: CGFloat) -> CGFloat { 50 }
|
||||
}
|
||||
|
||||
class ChatResponseTokenCell: ChatSettingBaseCell, CellConfigurable {
|
||||
|
||||
lazy var countLab: UILabel = {
|
||||
let lab = UILabel()
|
||||
lab.textColor = UIColor(hex: "#666666")
|
||||
lab.font = UIFont.boldSystemFont(ofSize: 14)
|
||||
lab.text = "0"
|
||||
lab.textAlignment = .center
|
||||
return lab
|
||||
}()
|
||||
|
||||
lazy var subBtn: UIButton = {
|
||||
let btn = UIButton()
|
||||
btn.setImage(UIImage(named: "role_setting_sub"), for: .normal)
|
||||
btn.addTarget(self, action: #selector(subBtnTap), for: .touchUpInside)
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var addBtn: UIButton = {
|
||||
let btn = UIButton()
|
||||
btn.setImage(UIImage(named: "role_setting_add"), for: .normal)
|
||||
btn.addTarget(self, action: #selector(addBtnTap), for: .touchUpInside)
|
||||
return btn
|
||||
}()
|
||||
|
||||
@objc func subBtnTap() {
|
||||
|
||||
}
|
||||
|
||||
@objc func addBtnTap() {
|
||||
|
||||
}
|
||||
|
||||
func configure(with row: RowModel) {
|
||||
guard let row = row as? TokenRow else { return }
|
||||
countLab.text = row.count
|
||||
}
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
configureViews()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func configureViews() {
|
||||
|
||||
containerView.addSubview(subBtn)
|
||||
containerView.addSubview(countLab)
|
||||
containerView.addSubview(addBtn)
|
||||
|
||||
countLab.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.left.equalTo(subBtn.snp.right).offset(5)
|
||||
make.right.equalTo(addBtn.snp.left).offset(-5)
|
||||
}
|
||||
|
||||
subBtn.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.left.equalToSuperview().inset(10)
|
||||
make.width.equalTo(25)
|
||||
make.height.equalTo(25)
|
||||
}
|
||||
|
||||
addBtn.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.right.equalToSuperview().inset(10)
|
||||
make.width.equalTo(25)
|
||||
make.height.equalTo(25)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// ChatSettingBaseCell.swift
|
||||
// Visual_Novel_iOS
|
||||
//
|
||||
// Created by mh on 2025/10/27.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class ChatSettingBaseCell: UITableViewCell {
|
||||
|
||||
lazy var containerView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = UIColor(hex: "#F6F6F6")
|
||||
view.cornerRadius = 15.0
|
||||
return view
|
||||
}()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
setupViews()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setupViews() {
|
||||
|
||||
contentView.addSubview(containerView)
|
||||
|
||||
containerView.snp.makeConstraints { make in
|
||||
make.left.right.equalToSuperview().inset(20)
|
||||
make.top.bottom.equalToSuperview().inset(2.5)
|
||||
make.height.equalTo(45.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
//
|
||||
// ChatSwipeCell.swift
|
||||
// Visual_Novel_iOS
|
||||
//
|
||||
// Created by mh on 2025/10/27.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
// icon title 声音头像 箭头
|
||||
struct ImageRow: RowModel {
|
||||
let icon: String
|
||||
let title: String
|
||||
let showAvatar: Bool
|
||||
let showArrow: Bool
|
||||
let showSwitch: Bool
|
||||
var cellReuseID: String { "ChatSwipeCell" }
|
||||
func cellHeight(tableWidth: CGFloat) -> CGFloat { 50 }
|
||||
}
|
||||
|
||||
class ChatSwipeCell: ChatSettingBaseCell, CellConfigurable {
|
||||
|
||||
lazy var iconImgView: UIImageView = {
|
||||
let imgView = UIImageView(image: UIImage(named: "role_exchange_mode"))
|
||||
return imgView
|
||||
}()
|
||||
|
||||
lazy var titleLab: UILabel = {
|
||||
let lab = UILabel()
|
||||
lab.text = "XL-0826-32K"
|
||||
lab.font = UIFont.boldSystemFont(ofSize: 14)
|
||||
lab.textColor = UIColor(hex: "#666666")
|
||||
return lab
|
||||
}()
|
||||
|
||||
lazy var avatarView : UIImageView = {
|
||||
let imgView = UIImageView()
|
||||
imgView.cornerRadius = 10.5
|
||||
imgView.backgroundColor = .blue
|
||||
return imgView
|
||||
}()
|
||||
|
||||
lazy var arrowImgView: UIImageView = {
|
||||
let imgView = UIImageView(image: UIImage(named: "role_setting_go"))
|
||||
return imgView
|
||||
}()
|
||||
|
||||
lazy var switchControl: SevenSwitch = {
|
||||
let con = SevenSwitch()
|
||||
con.onTintColor = UIColor(hex: "#020025")
|
||||
con.onThumbTintColor = UIColor(hex: "#00CC88")
|
||||
con.inactiveColor = UIColor(hex: "#020025")
|
||||
return con
|
||||
}()
|
||||
|
||||
lazy var stackView: UIStackView = {
|
||||
let stackView = UIStackView(arrangedSubviews: [avatarView, arrowImgView])
|
||||
stackView.spacing = 5
|
||||
stackView.distribution = .fill
|
||||
stackView.alignment = .center
|
||||
return stackView
|
||||
}()
|
||||
|
||||
func configure(with row: RowModel) {
|
||||
guard let row = row as? ImageRow else { return }
|
||||
iconImgView.image = UIImage(named: row.icon)
|
||||
titleLab.text = row.title
|
||||
avatarView.isHidden = !row.showAvatar
|
||||
arrowImgView.isHidden = !row.showArrow
|
||||
switchControl.isHidden = !row.showSwitch
|
||||
}
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
configureViews()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func configureViews() {
|
||||
containerView.addSubview(iconImgView)
|
||||
containerView.addSubview(titleLab)
|
||||
containerView.addSubview(stackView)
|
||||
containerView.addSubview(switchControl)
|
||||
|
||||
iconImgView.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.left.equalToSuperview().offset(12)
|
||||
make.width.height.equalTo(21)
|
||||
}
|
||||
|
||||
avatarView.snp.makeConstraints { make in
|
||||
make.width.height.equalTo(21)
|
||||
}
|
||||
|
||||
stackView.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.right.equalToSuperview().inset(15)
|
||||
}
|
||||
|
||||
titleLab.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.left.equalTo(iconImgView.snp.right).offset(9)
|
||||
make.right.equalTo(stackView.snp.left).offset(-5)
|
||||
}
|
||||
|
||||
switchControl.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.right.equalToSuperview().inset(15)
|
||||
make.width.equalTo(45)
|
||||
make.height.equalTo(23)
|
||||
}
|
||||
|
||||
titleLab.setContentCompressionResistancePriority(.fittingSizeLevel, for: .horizontal)
|
||||
titleLab.setContentHuggingPriority(.fittingSizeLevel, for: .horizontal)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// ActionProtocol.swift
|
||||
// Visual_Novel_iOS
|
||||
//
|
||||
// Created by mh on 2025/10/27.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 1. 数据协议:每行只要告诉我复用ID 和 高度
|
||||
protocol RowModel {
|
||||
var cellReuseID: String { get }
|
||||
func cellHeight(tableWidth: CGFloat) -> CGFloat
|
||||
}
|
||||
|
||||
/// 2. 渲染协议:Cell 自己负责填数据
|
||||
protocol CellConfigurable: AnyObject {
|
||||
func configure(with row: RowModel)
|
||||
}
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
//
|
||||
// ChatSettingSwipeView.swift
|
||||
// Visual_Novel_iOS
|
||||
//
|
||||
// Created by mh on 2025/10/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class ChatSettingSwipeView: CLContainer {
|
||||
|
||||
var closeAction: (()->Void)?
|
||||
|
||||
var sectionTitle: [String] = ["Switch Model", "Sound", "Maximum number of response tokens Maximum number of response tokens", "Appearance", "Background", "Historical Archives"]
|
||||
var rows: [[RowModel]] = [
|
||||
[ImageRow(icon: "role_exchange_mode", title: "XL-0826-32K", showAvatar: false, showArrow: true, showSwitch: false), ImageRow(icon: "role_text_mode", title: "Short Text Mode", showAvatar: false, showArrow: true, showSwitch: false)],
|
||||
[ImageRow(icon: "role_voice", title: "Voice actor", showAvatar: true, showArrow: true, showSwitch: false), ImageRow(icon: "role_talk", title: "Play dialogue only", showAvatar: false, showArrow: false, showSwitch: true)],
|
||||
[TokenRow(count: "2500")],
|
||||
[FontRow(count: "20", icon: "role_font", title: "Font size"), ImageRow(icon: "role_chat_mode", title: "Chat Mode", showAvatar: false, showArrow: true, showSwitch: false), ImageRow(icon: "role_chat_buttle", title: "Chat buttle", showAvatar: false, showArrow: true, showSwitch: false)],
|
||||
[BackgroundRow(count: 50)],
|
||||
[HistoryRow(time: "", icon: "", title: "", itemCount: 30)]
|
||||
]
|
||||
|
||||
|
||||
lazy var titleLab: UILabel = {
|
||||
let lab = UILabel()
|
||||
lab.text = "Setting"
|
||||
lab.font = UIFont.boldSystemFont(ofSize: 18)
|
||||
lab.textColor = UIColor(hex: "#000000")
|
||||
return lab
|
||||
}()
|
||||
|
||||
lazy var closeBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.setImage(UIImage(named: "role_chat_close"), for: .normal)
|
||||
btn.addTarget(self, action: #selector(closeBtnTap), for: .touchUpInside)
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var newChatBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.setBackgroundImage(UIImage(named: "role_new_chat"), for: .normal)
|
||||
btn.addTarget(self, action: #selector(newChatBtnTap), for: .touchUpInside)
|
||||
btn.setTitle("+ Start New Chat", for: .normal)
|
||||
btn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 17)
|
||||
btn.imageView?.contentMode = .scaleToFill
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var tableView: UITableView = {
|
||||
let tableView = UITableView(frame: .zero, style: .grouped)
|
||||
tableView.separatorStyle = .none
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
tableView.estimatedRowHeight = 72
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.backgroundColor = .clear
|
||||
tableView.showsVerticalScrollIndicator = false
|
||||
tableView.tableFooterView = UIView()
|
||||
tableView.register(ChatSwipeCell.self, forCellReuseIdentifier: "ChatSwipeCell")
|
||||
tableView.register(ChatResponseTokenCell.self, forCellReuseIdentifier: "ChatResponseTokenCell")
|
||||
tableView.register(ChatFontCell.self, forCellReuseIdentifier: "ChatFontCell")
|
||||
tableView.register(ChatBackgroundCell.self, forCellReuseIdentifier: "ChatBackgroundCell")
|
||||
tableView.register(ChatHistoryCell.self, forCellReuseIdentifier: "ChatHistoryCell")
|
||||
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
|
||||
return tableView
|
||||
}()
|
||||
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
setupViews()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setupViews() {
|
||||
|
||||
navigationView = NavigationView()
|
||||
navigationView?.backButton.isHidden = false
|
||||
navigationView?.isHidden = true
|
||||
addSubview(navigationView ?? UIView())
|
||||
|
||||
addSubview(titleLab)
|
||||
addSubview(closeBtn)
|
||||
addSubview(tableView)
|
||||
addSubview(newChatBtn)
|
||||
|
||||
navigationView?.snp.makeConstraints { make in
|
||||
make.top.equalToSuperview()
|
||||
make.left.equalToSuperview()
|
||||
make.right.equalToSuperview()
|
||||
make.height.equalTo(UIWindow.navBarTotalHeight)
|
||||
}
|
||||
|
||||
titleLab.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(20)
|
||||
if self.navigationView != nil {
|
||||
make.centerY.equalTo(self.navigationView!.titleLabel.snp.centerY)
|
||||
} else {
|
||||
make.top.equalToSuperview().offset(UIDevice().statusBarHeight + 13.0)
|
||||
}
|
||||
|
||||
make.right.equalTo(closeBtn.snp.left).offset(-10)
|
||||
}
|
||||
|
||||
closeBtn.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(titleLab.snp.centerY)
|
||||
make.right.equalToSuperview().inset(20)
|
||||
make.width.height.equalTo(25)
|
||||
}
|
||||
|
||||
|
||||
newChatBtn.snp.makeConstraints { make in
|
||||
make.left.right.equalToSuperview().inset(20)
|
||||
make.height.equalTo(50)
|
||||
make.bottom.equalToSuperview().inset(UIDevice().safeBottom + 5.0)
|
||||
}
|
||||
|
||||
tableView.snp.makeConstraints { make in
|
||||
make.left.right.equalToSuperview()
|
||||
make.top.equalTo(closeBtn.snp.bottom).offset(10)
|
||||
make.bottom.equalTo(newChatBtn.snp.top).offset(-5)
|
||||
}
|
||||
|
||||
// 设置tableView的contentInset,让内容可以滑动到最顶部
|
||||
tableView.contentInsetAdjustmentBehavior = .never
|
||||
tableView.automaticallyAdjustsScrollIndicatorInsets = false
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension ChatSettingSwipeView {
|
||||
|
||||
@objc func closeBtnTap() {
|
||||
self.closeAction?()
|
||||
}
|
||||
|
||||
@objc func newChatBtnTap() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension ChatSettingSwipeView: UITableViewDelegate, UITableViewDataSource {
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
// 确保tableView内容可以滑动到最顶部
|
||||
// 不需要额外的contentInset调整
|
||||
}
|
||||
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return rows.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return rows[section].count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let row = rows[indexPath.section][indexPath.row]
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: row.cellReuseID, for: indexPath)
|
||||
cell.selectionStyle = .none
|
||||
(cell as? CellConfigurable)?.configure(with: row)
|
||||
return cell
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
let header = UIView()
|
||||
|
||||
let lab = UILabel()
|
||||
lab.text = sectionTitle[section]
|
||||
lab.textColor = UIColor(hex: "#333333")
|
||||
lab.font = UIFont.boldSystemFont(ofSize: 16)
|
||||
lab.numberOfLines = 0
|
||||
header.addSubview(lab)
|
||||
lab.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(20)
|
||||
make.right.equalToSuperview()
|
||||
make.top.equalToSuperview().inset(5)
|
||||
make.bottom.equalToSuperview()
|
||||
}
|
||||
|
||||
return header
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
||||
return UIView()
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
|
||||
return CGFLOAT_MIN
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,543 @@
|
|||
//
|
||||
// SevenSwitch.swift
|
||||
//
|
||||
// Created by Benjamin Vogelzang on 6/20/14.
|
||||
// Copyright (c) 2014 Ben Vogelzang. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import QuartzCore
|
||||
|
||||
@IBDesignable @objc open class SevenSwitch: UIControl {
|
||||
|
||||
// public
|
||||
|
||||
/*
|
||||
* Set (without animation) whether the switch is on or off
|
||||
*/
|
||||
@IBInspectable open var on: Bool {
|
||||
get {
|
||||
return switchValue
|
||||
}
|
||||
set {
|
||||
switchValue = newValue
|
||||
self.setOn(newValue, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the background color that shows when the switch off and actively being touched.
|
||||
* Defaults to light gray.
|
||||
*/
|
||||
@IBInspectable open var activeColor: UIColor = UIColor(red: 0.89, green: 0.89, blue: 0.89, alpha: 1) {
|
||||
willSet {
|
||||
if self.on && !self.isTracking {
|
||||
backgroundView.backgroundColor = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the background color when the switch is off.
|
||||
* Defaults to clear color.
|
||||
*/
|
||||
@IBInspectable open var inactiveColor: UIColor = UIColor.clear {
|
||||
willSet {
|
||||
if !self.on && !self.isTracking {
|
||||
backgroundView.backgroundColor = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the background color that shows when the switch is on.
|
||||
* Defaults to green.
|
||||
*/
|
||||
@IBInspectable open var onTintColor: UIColor = UIColor(red: 0.3, green: 0.85, blue: 0.39, alpha: 1) {
|
||||
willSet {
|
||||
if self.on && !self.isTracking {
|
||||
backgroundView.backgroundColor = newValue
|
||||
backgroundView.layer.borderColor = newValue.cgColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the border color that shows when the switch is off. Defaults to light gray.
|
||||
*/
|
||||
@IBInspectable open var bordersColor: UIColor = UIColor(red: 0.78, green: 0.78, blue: 0.8, alpha: 1) {
|
||||
willSet {
|
||||
if !self.on {
|
||||
backgroundView.layer.borderColor = newValue.cgColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the knob color. Defaults to white.
|
||||
*/
|
||||
@IBInspectable open var thumbTintColor: UIColor = UIColor.white {
|
||||
willSet {
|
||||
if !userDidSpecifyOnThumbTintColor {
|
||||
onThumbTintColor = newValue
|
||||
}
|
||||
if (!userDidSpecifyOnThumbTintColor || !self.on) && !self.isTracking {
|
||||
thumbView.backgroundColor = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the knob color that shows when the switch is on. Defaults to white.
|
||||
*/
|
||||
@IBInspectable open var onThumbTintColor: UIColor = UIColor.white {
|
||||
willSet {
|
||||
userDidSpecifyOnThumbTintColor = true
|
||||
if self.on && !self.isTracking {
|
||||
thumbView.backgroundColor = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the shadow color of the knob. Defaults to gray.
|
||||
*/
|
||||
@IBInspectable open var shadowColor: UIColor = UIColor.gray {
|
||||
willSet {
|
||||
thumbView.layer.shadowColor = newValue.cgColor
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets whether or not the switch edges are rounded.
|
||||
* Set to NO to get a stylish square switch.
|
||||
* Defaults to YES.
|
||||
*/
|
||||
@IBInspectable open var isRounded: Bool = true {
|
||||
willSet {
|
||||
if newValue {
|
||||
backgroundView.layer.cornerRadius = self.frame.size.height * 0.5
|
||||
thumbView.layer.cornerRadius = (self.frame.size.height * 0.5) - 1
|
||||
}
|
||||
else {
|
||||
backgroundView.layer.cornerRadius = 2
|
||||
thumbView.layer.cornerRadius = 2
|
||||
}
|
||||
|
||||
thumbView.layer.shadowPath = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the image that shows on the switch thumb.
|
||||
*/
|
||||
@IBInspectable open var thumbImage: UIImage! {
|
||||
willSet {
|
||||
thumbImageView.image = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the image that shows when the switch is on.
|
||||
* The image is centered in the area not covered by the knob.
|
||||
* Make sure to size your images appropriately.
|
||||
*/
|
||||
@IBInspectable open var onImage: UIImage! {
|
||||
willSet {
|
||||
onImageView.image = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the image that shows when the switch is off.
|
||||
* The image is centered in the area not covered by the knob.
|
||||
* Make sure to size your images appropriately.
|
||||
*/
|
||||
@IBInspectable open var offImage: UIImage! {
|
||||
willSet {
|
||||
offImageView.image = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the text that shows when the switch is on.
|
||||
* The text is centered in the area not covered by the knob.
|
||||
*/
|
||||
open var onLabel: UILabel!
|
||||
|
||||
/*
|
||||
* Sets the text that shows when the switch is off.
|
||||
* The text is centered in the area not covered by the knob.
|
||||
*/
|
||||
open var offLabel: UILabel!
|
||||
|
||||
// internal
|
||||
internal var backgroundView: UIView!
|
||||
internal var thumbView: UIView!
|
||||
internal var onImageView: UIImageView!
|
||||
internal var offImageView: UIImageView!
|
||||
internal var thumbImageView: UIImageView!
|
||||
// private
|
||||
fileprivate var currentVisualValue: Bool = false
|
||||
fileprivate var startTrackingValue: Bool = false
|
||||
fileprivate var didChangeWhileTracking: Bool = false
|
||||
fileprivate var isAnimating: Bool = false
|
||||
fileprivate var userDidSpecifyOnThumbTintColor: Bool = false
|
||||
fileprivate var switchValue: Bool = false
|
||||
|
||||
/*
|
||||
* Initialization
|
||||
*/
|
||||
public convenience init() {
|
||||
self.init(frame: CGRect(x: 0, y: 0, width: 50, height: 30))
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
self.setup()
|
||||
}
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
let initialFrame = frame.isEmpty ? CGRect(x: 0, y: 0, width: 50, height: 30) : frame
|
||||
super.init(frame: initialFrame)
|
||||
|
||||
self.setup()
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Setup the individual elements of the switch and set default values
|
||||
*/
|
||||
fileprivate func setup() {
|
||||
|
||||
// background
|
||||
self.backgroundView = UIView(frame: CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height))
|
||||
backgroundView.backgroundColor = UIColor.clear
|
||||
backgroundView.layer.cornerRadius = self.frame.size.height * 0.5
|
||||
backgroundView.layer.borderColor = self.bordersColor.cgColor
|
||||
backgroundView.layer.borderWidth = 0.0
|
||||
backgroundView.isUserInteractionEnabled = false
|
||||
backgroundView.clipsToBounds = true
|
||||
self.addSubview(backgroundView)
|
||||
|
||||
// on/off images
|
||||
self.onImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.frame.size.width - self.frame.size.height, height: self.frame.size.height))
|
||||
onImageView.alpha = 1.0
|
||||
onImageView.contentMode = UIView.ContentMode.center
|
||||
backgroundView.addSubview(onImageView)
|
||||
|
||||
self.offImageView = UIImageView(frame: CGRect(x: self.frame.size.height, y: 0, width: self.frame.size.width - self.frame.size.height, height: self.frame.size.height))
|
||||
offImageView.alpha = 1.0
|
||||
offImageView.contentMode = UIView.ContentMode.center
|
||||
backgroundView.addSubview(offImageView)
|
||||
|
||||
// labels
|
||||
self.onLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.frame.size.width - self.frame.size.height, height: self.frame.size.height))
|
||||
onLabel.textAlignment = NSTextAlignment.center
|
||||
onLabel.textColor = UIColor.lightGray
|
||||
onLabel.font = UIFont.systemFont(ofSize: 12)
|
||||
backgroundView.addSubview(onLabel)
|
||||
|
||||
self.offLabel = UILabel(frame: CGRect(x: self.frame.size.height, y: 0, width: self.frame.size.width - self.frame.size.height, height: self.frame.size.height))
|
||||
offLabel.textAlignment = NSTextAlignment.center
|
||||
offLabel.textColor = UIColor.lightGray
|
||||
offLabel.font = UIFont.systemFont(ofSize: 12)
|
||||
backgroundView.addSubview(offLabel)
|
||||
|
||||
// thumb
|
||||
self.thumbView = UIView(frame: CGRect(x: 1, y: 1, width: self.frame.size.height - 2, height: self.frame.size.height - 2))
|
||||
thumbView.backgroundColor = self.thumbTintColor
|
||||
thumbView.layer.cornerRadius = (self.frame.size.height * 0.5) - 1
|
||||
thumbView.layer.shadowColor = self.shadowColor.cgColor
|
||||
thumbView.layer.shadowRadius = 2.0
|
||||
thumbView.layer.shadowOpacity = 0.5
|
||||
thumbView.layer.shadowOffset = CGSize(width: 0, height: 3)
|
||||
thumbView.layer.shadowPath = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath
|
||||
thumbView.layer.masksToBounds = false
|
||||
thumbView.isUserInteractionEnabled = false
|
||||
self.addSubview(thumbView)
|
||||
|
||||
// thumb image
|
||||
self.thumbImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: thumbView.frame.size.width, height: thumbView.frame.size.height))
|
||||
thumbImageView.contentMode = UIView.ContentMode.center
|
||||
thumbImageView.autoresizingMask = UIView.AutoresizingMask.flexibleWidth
|
||||
thumbView.addSubview(thumbImageView)
|
||||
|
||||
self.on = false
|
||||
}
|
||||
|
||||
override open func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||
super.beginTracking(touch, with: event)
|
||||
|
||||
startTrackingValue = self.on
|
||||
didChangeWhileTracking = false
|
||||
|
||||
let activeKnobWidth = self.bounds.size.height - 2 + 5
|
||||
isAnimating = true
|
||||
|
||||
UIView.animate(withDuration: 0.3, delay: 0.0, options: [UIView.AnimationOptions.curveEaseOut, UIView.AnimationOptions.beginFromCurrentState], animations: {
|
||||
if self.on {
|
||||
self.thumbView.frame = CGRect(x: self.bounds.size.width - (activeKnobWidth + 1), y: self.thumbView.frame.origin.y, width: activeKnobWidth, height: self.thumbView.frame.size.height)
|
||||
self.backgroundView.backgroundColor = self.onTintColor
|
||||
self.thumbView.backgroundColor = self.onThumbTintColor
|
||||
}
|
||||
else {
|
||||
self.thumbView.frame = CGRect(x: self.thumbView.frame.origin.x, y: self.thumbView.frame.origin.y, width: activeKnobWidth, height: self.thumbView.frame.size.height)
|
||||
self.backgroundView.backgroundColor = self.activeColor
|
||||
self.thumbView.backgroundColor = self.thumbTintColor
|
||||
}
|
||||
}, completion: { finished in
|
||||
self.isAnimating = false
|
||||
})
|
||||
|
||||
let shadowAnim = CABasicAnimation(keyPath: "shadowPath")
|
||||
shadowAnim.duration = 0.3
|
||||
shadowAnim.fromValue = thumbView.layer.shadowPath
|
||||
shadowAnim.toValue = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath
|
||||
thumbView.layer.add(shadowAnim, forKey: "shadowPath")
|
||||
thumbView.layer.shadowPath = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override open func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||
super.continueTracking(touch, with: event)
|
||||
|
||||
// Get touch location
|
||||
let lastPoint = touch.location(in: self)
|
||||
|
||||
// update the switch to the correct visuals depending on if
|
||||
// they moved their touch to the right or left side of the switch
|
||||
if lastPoint.x > self.bounds.size.width * 0.5 {
|
||||
self.showOn(true)
|
||||
if !startTrackingValue {
|
||||
didChangeWhileTracking = true
|
||||
}
|
||||
}
|
||||
else {
|
||||
self.showOff(true)
|
||||
if startTrackingValue {
|
||||
didChangeWhileTracking = true
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override open func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
||||
super.endTracking(touch, with: event)
|
||||
|
||||
let previousValue = self.on
|
||||
|
||||
if didChangeWhileTracking {
|
||||
self.setOn(currentVisualValue, animated: true)
|
||||
}
|
||||
else {
|
||||
self.setOn(!self.on, animated: true)
|
||||
}
|
||||
|
||||
if previousValue != self.on {
|
||||
self.sendActions(for: UIControl.Event.valueChanged)
|
||||
}
|
||||
}
|
||||
|
||||
override open func cancelTracking(with event: UIEvent?) {
|
||||
super.cancelTracking(with: event)
|
||||
|
||||
// just animate back to the original value
|
||||
if self.on {
|
||||
self.showOn(true)
|
||||
}
|
||||
else {
|
||||
self.showOff(true)
|
||||
}
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
if !isAnimating {
|
||||
let frame = self.frame
|
||||
|
||||
// background
|
||||
backgroundView.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height)
|
||||
backgroundView.layer.cornerRadius = self.isRounded ? frame.size.height * 0.5 : 2
|
||||
|
||||
// images
|
||||
onImageView.frame = CGRect(x: 0, y: 0, width: frame.size.width - frame.size.height, height: frame.size.height)
|
||||
offImageView.frame = CGRect(x: frame.size.height, y: 0, width: frame.size.width - frame.size.height, height: frame.size.height)
|
||||
self.onLabel.frame = CGRect(x: 0, y: 0, width: frame.size.width - frame.size.height, height: frame.size.height)
|
||||
self.offLabel.frame = CGRect(x: frame.size.height, y: 0, width: frame.size.width - frame.size.height, height: frame.size.height)
|
||||
|
||||
// thumb
|
||||
let normalKnobWidth = frame.size.height - 2
|
||||
if self.on {
|
||||
thumbView.frame = CGRect(x: frame.size.width - (normalKnobWidth + 1), y: 1, width: frame.size.height - 2, height: normalKnobWidth)
|
||||
thumbImageView.frame = CGRect(x: frame.size.width - normalKnobWidth, y: 0, width: normalKnobWidth, height: normalKnobWidth)
|
||||
}
|
||||
else {
|
||||
thumbView.frame = CGRect(x: 1, y: 1, width: normalKnobWidth, height: normalKnobWidth)
|
||||
thumbImageView.frame = CGRect(x: 0, y: 0, width: normalKnobWidth, height: normalKnobWidth)
|
||||
}
|
||||
|
||||
thumbView.layer.cornerRadius = self.isRounded ? (frame.size.height * 0.5) - 1 : 2
|
||||
thumbView.layer.shadowPath = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the state of the switch to on or off, optionally animating the transition.
|
||||
*/
|
||||
open func setOn(_ isOn: Bool, animated: Bool) {
|
||||
switchValue = isOn
|
||||
|
||||
if on {
|
||||
self.showOn(animated)
|
||||
}
|
||||
else {
|
||||
self.showOff(animated)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Detects whether the switch is on or off
|
||||
*
|
||||
* @return BOOL YES if switch is on. NO if switch is off
|
||||
*/
|
||||
open func isOn() -> Bool {
|
||||
return self.on
|
||||
}
|
||||
|
||||
/*
|
||||
* update the looks of the switch to be in the on position
|
||||
* optionally make it animated
|
||||
*/
|
||||
fileprivate func showOn(_ animated: Bool) {
|
||||
let normalKnobWidth = self.bounds.size.height - 2
|
||||
let activeKnobWidth = normalKnobWidth + 5
|
||||
if animated {
|
||||
isAnimating = true
|
||||
UIView.animate(withDuration: 0.3, delay: 0.0, options: [UIView.AnimationOptions.curveEaseOut, UIView.AnimationOptions.beginFromCurrentState], animations: {
|
||||
if self.isTracking {
|
||||
self.thumbView.frame = CGRect(x: self.bounds.size.width - (activeKnobWidth + 1), y: self.thumbView.frame.origin.y, width: activeKnobWidth, height: self.thumbView.frame.size.height)
|
||||
}
|
||||
else {
|
||||
self.thumbView.frame = CGRect(x: self.bounds.size.width - (normalKnobWidth + 1), y: self.thumbView.frame.origin.y, width: normalKnobWidth, height: self.thumbView.frame.size.height)
|
||||
}
|
||||
|
||||
self.backgroundView.backgroundColor = self.onTintColor
|
||||
self.backgroundView.layer.borderColor = self.onTintColor.cgColor
|
||||
self.thumbView.backgroundColor = self.onThumbTintColor
|
||||
self.onImageView.alpha = 1.0
|
||||
self.offImageView.alpha = 0
|
||||
self.onLabel.alpha = 1.0
|
||||
self.offLabel.alpha = 0
|
||||
}, completion: { finished in
|
||||
self.isAnimating = false
|
||||
})
|
||||
|
||||
let shadowAnim = CABasicAnimation(keyPath: "shadowPath")
|
||||
shadowAnim.duration = 0.3
|
||||
shadowAnim.fromValue = thumbView.layer.shadowPath
|
||||
shadowAnim.toValue = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath
|
||||
thumbView.layer.add(shadowAnim, forKey: "shadowPath")
|
||||
thumbView.layer.shadowPath = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath
|
||||
}
|
||||
else {
|
||||
if self.isTracking {
|
||||
thumbView.frame = CGRect(x: self.bounds.size.width - (activeKnobWidth + 1), y: thumbView.frame.origin.y, width: activeKnobWidth, height: thumbView.frame.size.height)
|
||||
}
|
||||
else {
|
||||
thumbView.frame = CGRect(x: self.bounds.size.width - (normalKnobWidth + 1), y: thumbView.frame.origin.y, width: normalKnobWidth, height: thumbView.frame.size.height)
|
||||
}
|
||||
|
||||
backgroundView.backgroundColor = self.onTintColor
|
||||
backgroundView.layer.borderColor = self.onTintColor.cgColor
|
||||
thumbView.backgroundColor = self.onThumbTintColor
|
||||
onImageView.alpha = 1.0
|
||||
offImageView.alpha = 0
|
||||
onLabel.alpha = 1.0
|
||||
offLabel.alpha = 0
|
||||
}
|
||||
|
||||
currentVisualValue = true
|
||||
}
|
||||
|
||||
/*
|
||||
* update the looks of the switch to be in the off position
|
||||
* optionally make it animated
|
||||
*/
|
||||
fileprivate func showOff(_ animated: Bool) {
|
||||
let normalKnobWidth = self.bounds.size.height - 2
|
||||
let activeKnobWidth = normalKnobWidth + 5
|
||||
|
||||
if animated {
|
||||
isAnimating = true
|
||||
UIView.animate(withDuration: 0.3, delay: 0.0, options: [UIView.AnimationOptions.curveEaseOut, UIView.AnimationOptions.beginFromCurrentState], animations: {
|
||||
if self.isTracking {
|
||||
self.thumbView.frame = CGRect(x: 1, y: self.thumbView.frame.origin.y, width: activeKnobWidth, height: self.thumbView.frame.size.height);
|
||||
self.backgroundView.backgroundColor = self.activeColor
|
||||
}
|
||||
else {
|
||||
self.thumbView.frame = CGRect(x: 1, y: self.thumbView.frame.origin.y, width: normalKnobWidth, height: self.thumbView.frame.size.height);
|
||||
self.backgroundView.backgroundColor = self.inactiveColor
|
||||
}
|
||||
|
||||
self.backgroundView.layer.borderColor = self.bordersColor.cgColor
|
||||
self.thumbView.backgroundColor = self.thumbTintColor
|
||||
self.onImageView.alpha = 0
|
||||
self.offImageView.alpha = 1.0
|
||||
self.onLabel.alpha = 0
|
||||
self.offLabel.alpha = 1.0
|
||||
|
||||
}, completion: { finished in
|
||||
self.isAnimating = false
|
||||
})
|
||||
|
||||
let shadowAnim = CABasicAnimation(keyPath: "shadowPath")
|
||||
shadowAnim.duration = 0.3
|
||||
shadowAnim.fromValue = thumbView.layer.shadowPath
|
||||
shadowAnim.toValue = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath
|
||||
thumbView.layer.add(shadowAnim, forKey: "shadowPath")
|
||||
thumbView.layer.shadowPath = UIBezierPath(roundedRect: thumbView.bounds, cornerRadius: thumbView.layer.cornerRadius).cgPath
|
||||
}
|
||||
else {
|
||||
if (self.isTracking) {
|
||||
thumbView.frame = CGRect(x: 1, y: thumbView.frame.origin.y, width: activeKnobWidth, height: thumbView.frame.size.height)
|
||||
backgroundView.backgroundColor = self.activeColor
|
||||
}
|
||||
else {
|
||||
thumbView.frame = CGRect(x: 1, y: thumbView.frame.origin.y, width: normalKnobWidth, height: thumbView.frame.size.height)
|
||||
backgroundView.backgroundColor = self.inactiveColor
|
||||
}
|
||||
backgroundView.layer.borderColor = self.bordersColor.cgColor
|
||||
thumbView.backgroundColor = self.thumbTintColor
|
||||
onImageView.alpha = 0
|
||||
offImageView.alpha = 1.0
|
||||
onLabel.alpha = 0
|
||||
offLabel.alpha = 1.0
|
||||
}
|
||||
|
||||
currentVisualValue = false
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ extension UIDevice {
|
|||
}
|
||||
|
||||
var hasNotch: Bool {
|
||||
if #available(iOS 15.0, *) {
|
||||
if #available(iOS 13.0, *) {
|
||||
// Use UIWindowScene.windows for iOS 15 and later
|
||||
let windowScene = UIApplication.shared.connectedScenes
|
||||
.compactMap { $0 as? UIWindowScene }
|
||||
|
|
@ -61,6 +61,19 @@ extension UIDevice {
|
|||
}
|
||||
}
|
||||
|
||||
var safeBottom: CGFloat {
|
||||
if #available(iOS 13.0, *) {
|
||||
// Use UIWindowScene.windows for iOS 15 and later
|
||||
let windowScene = UIApplication.shared.connectedScenes
|
||||
.compactMap { $0 as? UIWindowScene }
|
||||
.first
|
||||
return windowScene?.windows.first?.safeAreaInsets.bottom ?? 0.0
|
||||
} else {
|
||||
// Fallback for iOS versions before 15.0
|
||||
return UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0.0
|
||||
}
|
||||
}
|
||||
|
||||
// 状态栏 高度
|
||||
var statusBarHeight: CGFloat {
|
||||
if #available(iOS 13.0, *) {
|
||||
|
|
|
|||