From dfff3fe5cbc07711ddd98f0121538099198b3476 Mon Sep 17 00:00:00 2001 From: LeeMyeongJin Date: Wed, 30 Aug 2023 00:42:43 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[Feat]=20#178=20-=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B3=B5=EC=9C=A0=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Runnect-iOS/Podfile | 2 + Runnect-iOS/Podfile.lock | 51 +++-- .../Runnect-iOS.xcodeproj/project.pbxproj | 5 +- .../Global/Literal/ImageLiterals.swift | 5 + .../ic_follow_button.imageset/Contents.json | 23 ++ .../Property 1=follow 2.png | Bin 0 -> 1232 bytes .../Property 1=follow 2@2x.png | Bin 0 -> 2156 bytes .../Property 1=follow 2@3x.png | Bin 0 -> 2929 bytes .../ic_followed_button.imageset/Contents.json | 23 ++ .../ic_followed_button.imageset/follow 1.png | Bin 0 -> 1090 bytes .../follow 1@2x.png | Bin 0 -> 2078 bytes .../follow 1@3x.png | Bin 0 -> 2906 bytes .../ic_select_map.imageset/Contents.json | 23 ++ .../ic_select_map.imageset/btn_start map.png | Bin 0 -> 1881 bytes .../btn_start map@2x.png | Bin 0 -> 3487 bytes .../btn_start map@3x.png | Bin 0 -> 4958 bytes .../ic_select_now.imageset/Contents.json | 23 ++ .../ic_select_now.imageset/btn_start now.png | Bin 0 -> 2192 bytes .../btn_start now@2x.png | Bin 0 -> 4327 bytes .../btn_start now@3x.png | Bin 0 -> 6256 bytes .../ic_share.imageset/Contents.json | 23 ++ .../ic_share.imageset/btn_share.png | Bin 0 -> 413 bytes .../ic_share.imageset/btn_share@2x.png | Bin 0 -> 623 bytes .../ic_share.imageset/btn_share@3x.png | Bin 0 -> 990 bytes .../Global/Supports/AppDelegate.swift | 24 +++ .../UIComponents/CustomNavigationBar.swift | 5 + Runnect-iOS/Runnect-iOS/Info.plist | 1 + .../PickedMapListResponseDto.swift | 2 +- .../CourseDetail/VC/CourseDetailVC.swift | 198 +++++++++++++++++- .../Views/AdImageCollectionViewCell.swift | 3 +- .../CourseDrawing/VC/DepartureSearchVC.swift | 26 ++- 31 files changed, 407 insertions(+), 30 deletions(-) create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_follow_button.imageset/Contents.json create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_follow_button.imageset/Property 1=follow 2.png create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_follow_button.imageset/Property 1=follow 2@2x.png create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_follow_button.imageset/Property 1=follow 2@3x.png create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_followed_button.imageset/Contents.json create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_followed_button.imageset/follow 1.png create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_followed_button.imageset/follow 1@2x.png create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_followed_button.imageset/follow 1@3x.png create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_map.imageset/Contents.json create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_map.imageset/btn_start map.png create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_map.imageset/btn_start map@2x.png create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_map.imageset/btn_start map@3x.png create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_now.imageset/Contents.json create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_now.imageset/btn_start now.png create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_now.imageset/btn_start now@2x.png create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_now.imageset/btn_start now@3x.png create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_share.imageset/Contents.json create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_share.imageset/btn_share.png create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_share.imageset/btn_share@2x.png create mode 100644 Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_share.imageset/btn_share@3x.png diff --git a/Runnect-iOS/Podfile b/Runnect-iOS/Podfile index 3544d927..06d9b03f 100644 --- a/Runnect-iOS/Podfile +++ b/Runnect-iOS/Podfile @@ -13,6 +13,8 @@ target 'Runnect-iOS' do pod 'KakaoSDKCommon' pod 'KakaoSDKAuth' pod 'KakaoSDKUser' + pod 'KakaoSDKShare' + pod 'KakaoSDKTemplate' # Pods for Runnect-iOS diff --git a/Runnect-iOS/Podfile.lock b/Runnect-iOS/Podfile.lock index 90f88404..24d58004 100644 --- a/Runnect-iOS/Podfile.lock +++ b/Runnect-iOS/Podfile.lock @@ -1,23 +1,28 @@ PODS: - - Alamofire (5.6.4) - - KakaoSDKAuth (2.14.0): - - KakaoSDKCommon (= 2.14.0) - - KakaoSDKCommon (2.14.0): - - KakaoSDKCommon/Common (= 2.14.0) - - KakaoSDKCommon/Network (= 2.14.0) - - KakaoSDKCommon/Common (2.14.0) - - KakaoSDKCommon/Network (2.14.0): + - Alamofire (5.7.1) + - KakaoSDKAuth (2.16.0): + - KakaoSDKCommon (= 2.16.0) + - KakaoSDKCommon (2.16.0): + - KakaoSDKCommon/Common (= 2.16.0) + - KakaoSDKCommon/Network (= 2.16.0) + - KakaoSDKCommon/Common (2.16.0) + - KakaoSDKCommon/Network (2.16.0): - Alamofire (~> 5.1) - - KakaoSDKCommon/Common (= 2.14.0) - - KakaoSDKUser (2.14.0): - - KakaoSDKAuth (= 2.14.0) - - Kingfisher (7.4.1) + - KakaoSDKCommon/Common (= 2.16.0) + - KakaoSDKShare (2.16.0): + - KakaoSDKCommon (= 2.16.0) + - KakaoSDKTemplate (= 2.16.0) + - KakaoSDKTemplate (2.16.0): + - KakaoSDKCommon/Common (= 2.16.0) + - KakaoSDKUser (2.16.0): + - KakaoSDKAuth (= 2.16.0) + - Kingfisher (7.9.0) - Moya (15.0.0): - Moya/Core (= 15.0.0) - Moya/Core (15.0.0): - Alamofire (~> 5.0) - NMapsGeometry (1.0.1) - - NMapsMap (3.16.1): + - NMapsMap (3.17.0): - NMapsGeometry - SnapKit (5.6.0) - Then (3.0.0) @@ -25,6 +30,8 @@ PODS: DEPENDENCIES: - KakaoSDKAuth - KakaoSDKCommon + - KakaoSDKShare + - KakaoSDKTemplate - KakaoSDKUser - Kingfisher (~> 7.0) - Moya (~> 15.0) @@ -37,6 +44,8 @@ SPEC REPOS: - Alamofire - KakaoSDKAuth - KakaoSDKCommon + - KakaoSDKShare + - KakaoSDKTemplate - KakaoSDKUser - Kingfisher - Moya @@ -46,17 +55,19 @@ SPEC REPOS: - Then SPEC CHECKSUMS: - Alamofire: 4e95d97098eacb88856099c4fc79b526a299e48c - KakaoSDKAuth: 8fca87815de22062a23297983f327613b087b8bb - KakaoSDKCommon: 0ce638f7a2e49704943c0b74a087a9f8067bba1c - KakaoSDKUser: 2ca18314ce72e6690f76cb1f6f9fbc771d31a803 - Kingfisher: cd762a593a61b2fbecf7645c00f9a801a3ebfc9c + Alamofire: 0123a34370cb170936ae79a8df46cc62b2edeb88 + KakaoSDKAuth: 1b85ed7c41b0517bfd1fc9dc46c292c75b8cb610 + KakaoSDKCommon: d6579aa2e9d963d74e13d741cbf1cce48b8b0c17 + KakaoSDKShare: efc0415c4f33274232604eeaf96fb03641facdca + KakaoSDKTemplate: 7e478a3cda6f5879b475546bfcd667b464fbb6d0 + KakaoSDKUser: 427e5b3884abd19ee6d9a7bd6e3f3029eb2968b8 + Kingfisher: 59f908b6d2f403b0a3e539debb0eec05cb27002c Moya: 138f0573e53411fb3dc17016add0b748dfbd78ee NMapsGeometry: 53c573ead66466681cf123f99f698dc8071a4b83 - NMapsMap: 926c3a303d381a24bec8da3cd6e198f50af93ae9 + NMapsMap: a5b909a31b6f3d27a670f6eb2ddc913c38975474 SnapKit: e01d52ebb8ddbc333eefe2132acf85c8227d9c25 Then: 844265ae87834bbe1147d91d5d41a404da2ec27d -PODFILE CHECKSUM: fd71a741af1234cbd39c9117a0768b376c78640a +PODFILE CHECKSUM: 77a648fc451eb8403d22fda5ccf06419d697c921 COCOAPODS: 1.12.1 diff --git a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj index 8be6e36b..23d65d8e 100644 --- a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj +++ b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj @@ -1643,7 +1643,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Runnect-iOS/Runnect-iOS.entitlements"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 9K86FQHDLU; @@ -1658,6 +1658,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UIUserInterfaceStyle = Light; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1666,7 +1667,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.runnect.Runnect-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.runnect.Runnect-iOS"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.runnect.Runnect-iOS"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; diff --git a/Runnect-iOS/Runnect-iOS/Global/Literal/ImageLiterals.swift b/Runnect-iOS/Runnect-iOS/Global/Literal/ImageLiterals.swift index 0b1b7551..48a90d33 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Literal/ImageLiterals.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Literal/ImageLiterals.swift @@ -46,6 +46,11 @@ enum ImageLiterals { static var icPlus: UIImage { .load(named: "ic_plus") } static var icCheck: UIImage { .load(named: "ic_check") } static var icCheckFill: UIImage { .load(named: "ic_check_fill") } + static var icFollowButton: UIImage {.load(named: "ic_follow_button")} + static var icFollowedButton: UIImage {.load(named: "ic_followed_button")} + static var icShareButton: UIImage {.load(named: "ic_share")} + static var icSelectMapButton: UIImage {.load(named: "ic_select_map")} + static var icSelectNowButton: UIImage {.load(named: "ic_select_now")} // img static var imgBackground: UIImage { .load(named: "img_background") } diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_follow_button.imageset/Contents.json b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_follow_button.imageset/Contents.json new file mode 100644 index 00000000..42f4bfe2 --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_follow_button.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Property 1=follow 2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Property 1=follow 2@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Property 1=follow 2@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_follow_button.imageset/Property 1=follow 2.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_follow_button.imageset/Property 1=follow 2.png new file mode 100644 index 0000000000000000000000000000000000000000..9617123eb9ccc600ffcd15483ce395f4faf5a2fe GIT binary patch literal 1232 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 literal 0 HcmV?d00001 diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_followed_button.imageset/follow 1@2x.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_followed_button.imageset/follow 1@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..77099314ce76f66c973ac97331ebe1fbd51ac353 GIT binary patch literal 2078 zcmV+(2;ujMP)m-7tgNgY$Tqlq`SL!GZiY-

AO6@USbwxhp?rLw-QV8Fd0t?MvJIb>+&H z{>UcK6L z6Hr=ELq~e-Oh^j_!jgyk#9OWe4HwBUMtUl{Z=H#0;gI(2$o9|IuV0^j`t&I}bcu2( z`I0qxc+HnOW8uNAR##WoFI~EHQ1ovKJCum&p$O@%iD`MDt&fh5K79WCdAsP}6cUsS zagXK0d(LKWONpd_o}z&w?>%$K8-CCeXBeUbNiXc=y&+dnCMF|9QVWl+UAwlM^?IhD zhG~Jf&M;21o|OnqjiJ7O|K5=Z|AjM*bY7#8v=>qZC6WCLo6Q+Hw9rV}3#SCdQi`*L zrExahv~GqrpG2GBZ5}5*&K7Jkwf85WJR4} z#4+YlBZtDBoMFT>=29bvlC`=I%RJou`0>Mi{P;21Z?#&Bk-}U?bWWU04Cx?BL>`VkDi+_KJpxnHJ z%e;E^D(Y|U-Mi3N zjvkHe(KuDpMZjGP7kLT(Jr`T2IP=G)*Af?f91|MHcMTR>SiH|?C z3!hiY=9L~icrfvd3o-G#abiUE{Q2`lH2PaZM-1zzX0)2FLVQ8t9yF}qK)yJN^vtZr z^`B>-M%1Js8!jrNpEs%*|ihrznwA5j3* zkCxBR+@Hq`I1f%;Z*uV|SJ6xlsGM-vLu9@~m8q!MoK-jr%p%KOXz<bbqrD#X$_476eTN}>XSFyODjVbb;(N+ zkaN{LwWgQux#Rz%Glz$#55dn8$P%**S#WIeL zj#xXW?l=x(uE3o{Gd=&gF+t%{qPQ(*7zN-~9=Y9tp_+HDyTpvea%oc7sBs05H z=96_v#I4~yKMdm(!$3Us^3s{B!oxF)Np{+-&+y@TP`AUOPRYEJ0_!p&s7KB)Jj9(I zWr+!LZ_UBI)40&NGYoekCNY{`?t5GThBsNX>y|Gu(*9d#i79!XD+mk7h{~>ouyl}@ zykGPdvQdX>%JNL-!i5WV+36*L^9^J+cRw#?3W>U-Cm$@fcSCk}oU!QaNL$~I5fhX? zm>jo~#bj<3YYEWBqE8+&R!GXkXD8UrtQUs9Y&%6`r7Wg)x(klAy1M$L(P-SY7;A1I z;O6k}y-9_n%;fy3p=+_-m5qa>j|1Dd8S|alK9~Lf9VFNCDsQ>@~07*qo IM6N<$fW0PDd#^I_!=P)6vEG1je6$(9y-|=rA3fm5vUTbWu7wE4CS! z@gMR&^1aS#y(jIRR`|SI7SH=)R2qAS~$t)N@e*9Sb`t@u3;^Ja` zX=$k~x1VrK9$L#DZyQ1^WQ+{t#~}N4fZt`D{3Ev>?%%m{r)w6B8y6l#b$ooh(P%V& zmirw!v~CEA4@B6L3H?2Un2GhqxDnw&Y)3~&TOzh6@@vNsGJm=<4*zVmTHV{XZ}-id znXB-`vUTLgj-lAdVzCbquCA``n>jHT;bqoshw&L%WW@dKiz~V-0^Bn*W~RcU;7E$h zLx3|89$iXO1t7rX<>g1ShyfcWB3Xv=mE1ir6dg$%cxq>7XMdlZoV+?bJX|voiDvQS z#i0!Fj&3EXBC)Ms6pJUK`&9(@iJ~J_MXjHBbUP*jQ&~K9EoC;?GKACw8SlTZtgQTI zrl7R&vevEx`N>WbQb&5tW^=Qw>sd;8-@biopP!!}(6uCWW}3LKu<$54LjI?$_zDS+ zq9f!wWyBYp)kI7uqGP#$5Qi+|`L0NOl1$-}9zqHN%}BgA z*5%A~HII&tHblAa3?ZZ}xh`j}@Fa`*H?18ZJ4M}qH zrSGw87LVO2ghXW^#r6Al@7^8RPVkz=laroNbc94>t=Vi|tASs$c#Q1`iOw~vH)8P^ z+YvGo)}*|!Wgi$3o@8NuHH47ph(UW||9<6aVzVObn_|d~F>Y-rnBGv8}Bwv$hs> zgR7+I_xomle{_TA?d@dShOg>+P3s`7-2wpdkx?r2nQX|di#xXPI)()C<;xfI@#9Am zgZ1@w({8uTw0!>jdHA|!#e)YA%)NW}g3pIo^flSS!E@mK;&$uSEmJd|nQWEMP+~68o>I&+9+_J;Fj6oah#{X z%IQFhHQ?}fV-DV;aTVU9Pjvda@DtY1{v7o0Affc)#fw_Fo|)EuSBu9j9SVSfw6U=< zysxp0g2AJYDHvF`>jlxOaAWcK@#Enw7Vc{v2R;K=bod!gKq>;x6=KYaMG4pwgs{m_kzi;K1) z3kwFcds{TzqzitB73JbXIIUTuiS z=We=zz3XN|n2YA#=)jFZsGC;=xRW75!Do+~mN?Y?RO?>TwRkpzrUZI}R5P1E1A9BR zfXHm&`BK+0Gk$pPS*?YG8kf%ny`Ix_=tDpu?z7uOfd4bg zSXUbgd@X)1`nv4vrG@w^l^gJ7CU7Pft(#&1TZ&ydj#RcHK?ag2c9VAaB2sI^&8C*G9_>A;QVqA_B)!FSwtb zo%NddB8B7QW4ycMooKg{#g}QE#Y%)KtT2d)uNfU;DH5mY77?(F_N0bunzHc3&Fn#Q zYU=(n6%gK}IIHMNghi&Tm33V|T`5tNaq*V>H>PP;!2h$~r%rOIUPo~=p|z`)v3*m4 zE!=%87HsB~8N)z})mAocaL2H+?n;Rm_@2+###i>a5|8nqwM*d*ea>bx>DDo2obcvV zyYuZQQYTz@9^xqzoB5VlX_I-XjC^T{73XzK;`ml9fU+wQ$`Ui?%iAXJCTm;c2EV$N zc8sli!k`#Fsg(>FOEdR-*ZWsr(}g|V<;Q`k0KP%*F4u_(2H3d8vC|UT zoV-XtFj${AZUVyOp_7WbyyMPKeF>2WZ*z5Z^?z3rRLR|}%Rc;zjBaWR?<4PZ&7u@EFHkfWH}h(Cz7k&MZoZbgJBE-5=r-;U48*5>nxVkTYfD=N8pXTzFH80> zhlhu2V)YIUDa5RIcy{;JOc`xp_kCve%KX%mC@$hr-WTK6*WZ`i6U6GhGne07NeGF6 zxS4w^D=W|KV-3%FuMHt28mFhHyY8`T!b6=wvM+xcLdev>+SxAV)p6g+66>`k*85~C zkl`fcCUIjs#pi08>_obpgiOb-Pjp}@?|&e}*=n^urTX0pgxn;k+et#@e%|Aaya!O% z6VW|4gpk5WLgmr8#~b4w%o;j(4I!imxh|%%WCD=d9(p)Ch7dA|t{ALGlTK=w03 z96qZdgyaH^Kr2D#nm|rYPSz#ngXLq1fspGAM0CGQaxs-4iw7h_gT+;wh7jUoGgNdS zSa=}oYGJ065DRfJpXLrBem=)@b`GX*JR@s!oo)jh1HHJ4xU zMMxb%oqMy4=s+opr=X{^)oLBEyM@#VWG|&aj+m}zN>a+=DF`OB3VrDpKuAhpLt2da zX+$@C9uo;EvcELkHiA@}*OElpHW7#>Jb<0bkaVHzNvcHljar2>m*beq)pWrN6Epma z;gTX!9bRKh#}pliYVmB4cv)UvekrrXGebyvbkEMtew#Ly%VTE3Q?iY65&LxvA&XB} zZo9E9oXgCGr=ZvK;^JbP7LY6;s7H6rW%8tJ=D^&9r(`ZDk}&voqU66Cj3iB0jKO=k zVMC(1HiOJvcs7;@pO{Nmlq(;|EE!rh5!zojAvT-;09oJ>N~^ss9smFU07*qoM6N<$ Ef=9!S*Z=?k literal 0 HcmV?d00001 diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_map.imageset/Contents.json b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_map.imageset/Contents.json new file mode 100644 index 00000000..a15a5dbc --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_map.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "btn_start map.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "btn_start map@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_start map@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_map.imageset/btn_start map.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_map.imageset/btn_start map.png new file mode 100644 index 0000000000000000000000000000000000000000..843b9cfea12259267ea8f48cd20bda3d38091ac2 GIT binary patch literal 1881 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 literal 0 HcmV?d00001 diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_map.imageset/btn_start map@3x.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_map.imageset/btn_start map@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..0e97cad85985d81611843c30947fc2154d6def99 GIT binary patch literal 4958 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 literal 0 HcmV?d00001 diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_now.imageset/Contents.json b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_now.imageset/Contents.json new file mode 100644 index 00000000..e01fdee5 --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_now.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "btn_start now.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "btn_start now@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_start now@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_now.imageset/btn_start now.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_now.imageset/btn_start now.png new file mode 100644 index 0000000000000000000000000000000000000000..0c07bd43bad101b056723d7e4c3865a4577bb777 GIT binary patch literal 2192 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

b?V4g z*mik$M`s~P;!a`NNIqq+MRx@271_;moNWn|rYA+pkuN$gR;a4iY!D~@=?q+F#L$@Ufa7iu-J4m^btt)?#TYDv{+#6ytyxXk(sHub+ z(rO$gX_%=f$B;N1ap`~uH=3?+Utb9L+%`RY+k)WTGv`**liP-HE1Vd=4^OV$xLKvS z3?0hX&7s?TeI>!=q#TLg1LldEx( zh~<4{a>g>kX~kz?at;&tr#jHh0L+>>u;*V3tTVieC0lR`2QqFEkF%ojvvwPwWG+Yk zAeD;@GiAxbBz1MKt0biq#7_7ex4JY*yDXN6%K?a>ybkJc<8vKbp?M z{*1xy#bOoINu0fMmK=j&mwh66H5W1x=DB4^2z9mD1tETYtj{2ao(91~r^3#-H-!hC z>bG1ODO&wvvy+Eha$j_iA|9Le7ZqUpxp|k&r=+rSKCOE7%kMsy2-d=%d(x&wq}3Fe zoXr`_ioO&&X?fm=SOQ-2HRE2G{9tvP#$3E6dz#rF0i_nr+)Wh8^)b zH3q4wJ}TvsS9Fl`sFU%wapu7f&A#%U_h+lTca8Nh{E>9A;GlP&=;_~JCg<5G(5UNC zw5O|mFgqsrDKB&NuTG+)l3gWVOWPTRis#7^e&EoLUZ2jJ+XMB2&*&uNwI#ta^IZU9 zJ6GW;SG?rpHj~$F^&_jls-HtkBpJc}9 z47v;RA7L}$*BPe-#gH7GwZ4xuQ8fRIUyQgiw|Ihm?V|~O`A_Z|9S6~EO2oFzMMVQB zi^w7O@&uSrHiwC?4V6eHHst`m6WDfPJ8P?seag5xV&%d@d~P+@j&RPK`6-EHUbH{? zVKsL}CCs8H(J*dAn5y{^VF-s6x?DRsrw{b3Wb@hcdEvWAjzqQYG0G)hbvAB&XoTYaFbUy!osU>fVqPQpN4>=Z)tz5P!V?XLr1B z((WCXv5B!K$A>%4?iz*q)`%@08#ZrNSI|OB6Zi(z@Y4~@d%p;}=L6-0gIC>Spvd(g zcBN2LAIKrDlEhDJ@sgf;IM6eC>}y=qZHKMvd$#nZBn0U`-d?f127}+5=UzkR!2m+; z>?!E9~wN4IJq z{kQz80>FST&+VjWSWqQ_IHu Xgfe$jVc*xwzXXMmzNua{_z~(q;^-vS literal 0 HcmV?d00001 diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_now.imageset/btn_start now@3x.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_select_now.imageset/btn_start now@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..4ae540ab63a8234c8b655a26a571f0b3c969e180 GIT binary patch literal 6256 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 Ov4*O)O4Up2i2nhsJ7l^5 literal 0 HcmV?d00001 diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_share.imageset/Contents.json b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_share.imageset/Contents.json new file mode 100644 index 00000000..3853199f --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_share.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "btn_share.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "btn_share@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_share@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_share.imageset/btn_share.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_share.imageset/btn_share.png new file mode 100644 index 0000000000000000000000000000000000000000..fb931a3992b47b7c05faebb69d90957b2dada0ab GIT binary patch literal 413 zcmV;O0b>4%P)!nv0H`2z5ITVQ8e3})2%%N19pyji%N{3( z@vQwlfDl3mA%qaefVu8mTi2$tc_hz+bUqvThJ zM5|htxx~y_B9diA2SkM#^XM#)fO@uDUR{sq#8eyoENWrp&(Mjf8+byTbK2p6XcY{j z->2Xfzh7cQ5)APL>c!=}+UTnJPpW3lFuGE`>?xbMB_i>IG;_V!7X7vMZ+dm3?+CZD z5_24o$gK;IC;vyQ!Fx3;~l?hP~-8O-Ps z_Prk2aaut9>(}&-?*jHtW#x+dod0Y~5_}YRB5T>zzWn1pCl1|vw`1{h#d)tIw!FK` z?HPHl-}+|xmX80L7}F)UCJf8>1j ze-VGj;0xQIy^nf-e6@-+huTBii06AV{#M<$`nj^N^7TCNva*#|OZUd!UJ{jA zp&Z6(y>a`!d(X<+?N-n9+Y`qAqW*1ti1dbJtz*2R=igL&Tk2J%Nj}<^xBcyVMWYa# z-LH3~Hy>1)b-?GaC-THNLx7&r9E+K0Dy?*8j=wvn!?^QJK@Y_PM$4yvT@?H#t2+Z`CQ6DXTo)y07Bd z)!HpbGM^j^=_!u7%c<{d(^7Hw;r9*7+QD1?^ZKtfo6~dpOxcmQQ&O+{&FQh0SpJan zPLP=TW24=H<-0!0=I*@_p7QXD%`~G~kM4zJIeMFNSIy};oVLt_LF7Bv?QJ`@3rsF3 zx-Ig3&ji<{4|^k2Q=T3@x#N+>F-GL5Vqkc%;2*;?Epy|jD(e{_8BbR~mvv4FO#px_ B6D9xv literal 0 HcmV?d00001 diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_share.imageset/btn_share@3x.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/ic_share.imageset/btn_share@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..2ebf8f1979fb5ec34b0d5a44b229fdd8857a8b0a GIT binary patch literal 990 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q4M;wBd$a>caTa()7Bes?T7WR4>omgzpde#$ zkh>GZx^prwfgF}}M_)$E)e-c?47?_JaT^vIy7~kG~TPSQOaO@+ymIvpnhA9eK z4SY2r46-+5Rb*qV3_=pD6l5(zCX@)|sXsqxx!!Nr=KVju{6G7~KqB+Ugx8}fAuIb^<=S( z_QER1%finErj=}Wx*Bx(T3G(;xi^kp*OQwv?_9u2w>qaIF25D*A4)CsvYxBveBw*z zr}Fd_Jy*-yIxmQQyK7aOU-HzBE4ZTje!SM>t=zkcR4)AV(mv~dF1^QL>c>f{E6!Y1 z?`~(AmG-pen``QxvVJGgrc3FuVjCCO@7~~58~*P^()uqUVy?0)ytbuDN|)~0pu0D8 z!Qy*DDXU8kB_3RK%zbXyXTBhB?*1J?X0PsK{!uXbmMeYM+9S8cwM%OM?ah~8hS_A? zs#WlND4wTRc`juRXKCAlT@7)w@uOGG<*|||mw?Dl0Z}uOpO~?Q3 zKdkZj()Y)HU#4bEneX@Qm%Y@JZO^3_a(#=X9O=45#q~KaS(J-Q1MF z_T$8q<(pWn@6B5*Bpu1<^pWPGtQu6VXx$no-NW}DC5!aLvoQ&db^R4;ITcZhyQ zjTiUKe}Vtr@l3sWBA$QhgPHMjM3i{X|9PYXRGS3En=AIeN{GI({VLOShZ*O}{13Zy z%(QcSbLc|0$=f;FT~-2YL8gkcmsLCyk1U&Z)Of#z_u@+SmAwsd0zgBUq&}X}*K0_x eBO()Uf9Ic=d%)!76SsSy?BeO_=d#Wzp$Pzk$hsr| literal 0 HcmV?d00001 diff --git a/Runnect-iOS/Runnect-iOS/Global/Supports/AppDelegate.swift b/Runnect-iOS/Runnect-iOS/Global/Supports/AppDelegate.swift index 902b8147..6da1fca6 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Supports/AppDelegate.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Supports/AppDelegate.swift @@ -14,6 +14,8 @@ import KakaoSDKCommon @main class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { if let url = URLContexts.first?.url { if (AuthApi.isKakaoTalkLoginUrl(url)) { @@ -34,6 +36,28 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return true } + + // Handle deep linking + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + if let components = URLComponents(url: url, resolvingAgainstBaseURL: false), + let host = components.host, + let queryItems = components.queryItems { + + // Handle deep link URL here + if host == "detail" { + if let courseId = queryItems.first(where: { $0.name == "courseId" })?.value { + // Now you can navigate to your desired view controller based on the courseId + if let navigationController = window?.rootViewController as? UINavigationController, + let courseDetailVC = navigationController.viewControllers.first as? CourseDetailVC { + // Call a method in your CourseDetailVC to navigate to the desired view + courseDetailVC.navigateToCourseView(with: courseId) + } + } + } + } + return false + } + // MARK: UISceneSession Lifecycle diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/CustomNavigationBar.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/CustomNavigationBar.swift index 2608b3b9..4614e790 100644 --- a/Runnect-iOS/Runnect-iOS/Global/UIComponents/CustomNavigationBar.swift +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/CustomNavigationBar.swift @@ -56,6 +56,7 @@ final class CustomNavigationBar: UIView { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + } // MARK: - Methods @@ -69,6 +70,7 @@ extension CustomNavigationBar { } } + private func setAddTarget(_ type: NaviType) { self.naviType = type self.leftButton.addTarget(self, action: #selector(popToPreviousVC), for: .touchUpInside) @@ -315,4 +317,7 @@ extension CustomNavigationBar: UITextFieldDelegate { self.hideKeyboard() return true } + + + } diff --git a/Runnect-iOS/Runnect-iOS/Info.plist b/Runnect-iOS/Runnect-iOS/Info.plist index 26acf7a7..2c691327 100644 --- a/Runnect-iOS/Runnect-iOS/Info.plist +++ b/Runnect-iOS/Runnect-iOS/Info.plist @@ -26,6 +26,7 @@ CFBundleURLSchemes kakao27d01e20b51e5925bf386a6c5465849f + myapp diff --git a/Runnect-iOS/Runnect-iOS/Network/Dto/CourseDiscoveryDto/ResponseDto/PickedMapListResponseDto.swift b/Runnect-iOS/Runnect-iOS/Network/Dto/CourseDiscoveryDto/ResponseDto/PickedMapListResponseDto.swift index 481ed5af..37097778 100644 --- a/Runnect-iOS/Runnect-iOS/Network/Dto/CourseDiscoveryDto/ResponseDto/PickedMapListResponseDto.swift +++ b/Runnect-iOS/Runnect-iOS/Network/Dto/CourseDiscoveryDto/ResponseDto/PickedMapListResponseDto.swift @@ -1,5 +1,5 @@ // -// CourseDiscoveryResponsDto.swift +// CourseDiscoveryResponseDto.swift // Runnect-iOS // // Created by YEONOO on 2023/01/10. diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift index 6d106f2b..e9cac3e7 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift @@ -12,6 +12,9 @@ import Then import NMapsMap import Moya import SafariServices +import KakaoSDKCommon +import KakaoSDKShare +import KakaoSDKTemplate final class CourseDetailVC: UIViewController { @@ -31,6 +34,8 @@ final class CourseDetailVC: UIViewController { private var publicCourseId: Int? private var isMyCourse: Bool? + private var safariViewController : SFSafariViewController? + // MARK: - UI Components private lazy var navibar = CustomNavigationBar(self, type: .titleWithLeftButton) @@ -38,6 +43,10 @@ final class CourseDetailVC: UIViewController { $0.setImage(ImageLiterals.icMore, for: .normal) $0.tintColor = .g1 } + private let shareButton = UIButton(type: .system).then { + $0.setImage(ImageLiterals.icShareButton, for: .normal) + $0.tintColor = .g1 + } private lazy var middleScorollView = UIScrollView().then { $0.isScrollEnabled = true $0.showsVerticalScrollIndicator = false @@ -58,6 +67,11 @@ final class CourseDetailVC: UIViewController { $0.addTarget(self, action: #selector(startButtonDidTap), for: .touchUpInside) } + private lazy var followButton = UIButton(type: .custom).then { + $0.setImage(ImageLiterals.icFollowButton, for: .normal) + $0.setImage(ImageLiterals.icFollowedButton, for: .selected) + } + private let mapImageView = UIImageView() private let profileImageView = UIImageView().then { $0.image = ImageLiterals.imgStampC3 @@ -135,12 +149,133 @@ extension CourseDetailVC { scrapCourse(scrapTF: !sender.isSelected) } + @objc func followButtonTapped() { + followButton.isSelected.toggle() + } + +// @objc func shareButtonTapped() { +// guard let model = self.uploadedCourseDetailModel else { +// return +// } +// +// let courseImage = model.publicCourse.image +// let courseId = model.publicCourse.courseId +// let courseTitle = model.publicCourse.title +// let courseDescription = model.publicCourse.description +// +// // Create a deep link URL for your app +// let deepLinkURLString = "myapp://detail?courseId=\(courseId)" +// +// // 공유할 배열 생성 +// var itemsToShare: [Any] = [] +// +// // Add course description +//// itemsToShare.append(courseImage) +//// itemsToShare.append(courseTitle) +//// itemsToShare.append(courseDescription!) +// +// // Check if your app is installed +// if let deepLinkURL = URL(string: deepLinkURLString), +// UIApplication.shared.canOpenURL(deepLinkURL) { +// // Your app is installed, share the deep link +// itemsToShare.append(deepLinkURL) +// } else { +// // Your app is not installed, share the app store link +// let appBundleID = "com.runnect.Runnect-iOS" +// let appStoreURLString = "https://itunes.apple.com/app/id\(appBundleID)" +// +// if let appStoreURL = URL(string: appStoreURLString) { +// itemsToShare.append(appStoreURL) +// } +// } +// +// // Create an activity view controller +// let activityViewController = UIActivityViewController(activityItems: itemsToShare, applicationActivities: nil) +// +// // Remove the excludedActivityTypes array to include all options +// activityViewController.excludedActivityTypes = nil +// +// // Present the activity view controller +// if let popoverPresentationController = activityViewController.popoverPresentationController { +// popoverPresentationController.sourceView = self.view +// popoverPresentationController.sourceRect = self.shareButton.frame +// } +// +// present(activityViewController, animated: true, completion: nil) +// } + + @objc func shareButtonTapped() { + + guard let model = self.uploadedCourseDetailModel else { + return + } + + let title = model.publicCourse.title + let courseId = model.publicCourse.courseId + let courseImage = model.publicCourse.image + let courseDescription = model.publicCourse.description + let deepLinkURLString = "myapp://detail?courseId=\(courseId)" + + // Send the feed message using KakaoLink API + if ShareApi.isKakaoTalkSharingAvailable() { + + // Web Link로 전송이 된다. 하지만 우리는 앱 링크를 받을거기 때문에 딱히 필요가 없으. + // 아래 줄을 주석해도 상관없다. + let link = Link(mobileWebUrl: URL(string: deepLinkURLString)) + + // 우리가 원하는 앱으로 보내주는 링크이다. + // second, vvv는 url 링크 마지막에 딸려서 오기 때문에, 이 파라미터를 바탕으로 파싱해서 + // 앱단에서 원하는 기능을 만들어서 실행할 수 있다 예를 들면 다른 뷰 페이지로 이동 등등~ + let appLink = Link(iosExecutionParams: ["key1": "courseId=\(courseId)"]) + + // 해당 appLink를 들고 있을 버튼을 만들어준다. + let button = Button(title: "앱에서 보기", link: link) + + // Content는 이제 사진과 함께 글들이 적혀있다. + let content = Content(title: title, + imageUrl: URL(string: courseImage)!, + description: courseDescription, + link: appLink) + + // 템플릿에 버튼을 추가할때 아래 buttons에 배열의 형태로 넣어준다. + // 만약 버튼을 하나 더 추가하려면 버튼 변수를 만들고 [button, button2] 이런 식으로 진행하면 된다 . + let template = FeedTemplate(content: content, buttons: [button]) + + // 메시지 템플릿 encode + if let templateJsonData = (try? SdkJSONEncoder.custom.encode(template)) { + + // 생성한 메시지 템플릿 객체를 jsonObject로 변환 + if let templateJsonObject = SdkUtils.toJsonObject(templateJsonData) { + ShareApi.shared.shareDefault(templateObject: templateJsonObject) {(linkResult, error) in + if let error = error { + print("error : \(error)") + } else { + print("defaultLink(templateObject:templateJsonObject) success.") + guard let linkResult = linkResult else { return } + UIApplication.shared.open(linkResult.url, options: [:], completionHandler: nil) + } + } + } + } + } else { + // 없을 경우 카카오톡 앱스토어로 이동합니다. (이거 하려면 URL Scheme에 itms-apps 추가 해야함) + let appBundleID = "com.runnect.Runnect-iOS" + let appStoreURLString = "https://itunes.apple.com/app/id\(appBundleID)" + if let url = URL(string: appStoreURLString), UIApplication.shared.canOpenURL(url) { + if #available(iOS 10.0, *) { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } else { + UIApplication.shared.openURL(url) + } + } + } + } + @objc func startButtonDidTap() { guard handleVisitor() else { return } guard let courseId = self.courseId else { return } getCourseDetailWithPath(courseId: courseId) } - @objc func moreButtonDidTap() { guard let isMyCourse = self.isMyCourse, let uploadedCourseDetailModel = self.uploadedCourseDetailModel else { return } @@ -231,6 +366,8 @@ extension CourseDetailVC { private func setAddTarget() { likeButton.addTarget(self, action: #selector(likeButtonDidTap), for: .touchUpInside) moreButton.addTarget(self, action: #selector(moreButtonDidTap), for: .touchUpInside) + followButton.addTarget(self, action: #selector(followButtonTapped), for: .touchUpInside) + shareButton.addTarget(self, action: #selector(shareButtonTapped), for: .touchUpInside) } private func setNullUser() { @@ -239,6 +376,7 @@ extension CourseDetailVC { self.profileNameLabel.text = "(알 수 없음)" self.runningLevelLabel.isHidden = true } + } // MARK: - Layout Helpers @@ -248,6 +386,7 @@ extension CourseDetailVC { private func setNavigationBar() { view.addSubview(navibar) view.addSubview(moreButton) + view.addSubview(shareButton) navibar.snp.makeConstraints { make in make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) make.height.equalTo(48) @@ -256,7 +395,10 @@ extension CourseDetailVC { make.trailing.equalTo(self.view.safeAreaLayoutGuide) make.centerY.equalTo(navibar) } - + shareButton.snp.makeConstraints { make in + make.trailing.leading.equalTo(self.view.safeAreaLayoutGuide).offset(135) + make.centerY.equalTo(navibar) + } } private func setUI() { @@ -309,7 +451,7 @@ extension CourseDetailVC { make.bottom.equalTo(thirdHorizontalDivideLine.snp.top) } - middleScorollView.addSubviews(mapImageView, profileImageView, profileNameLabel, runningLevelLabel, firstHorizontalDivideLine, courseTitleLabel, courseDetailStackView, secondHorizontalDivideLine, courseExplanationTextView) + middleScorollView.addSubviews(mapImageView, profileImageView, profileNameLabel, runningLevelLabel, followButton, firstHorizontalDivideLine, courseTitleLabel, courseDetailStackView, secondHorizontalDivideLine, courseExplanationTextView) mapImageView.snp.makeConstraints { make in make.top.equalToSuperview() @@ -330,7 +472,12 @@ extension CourseDetailVC { runningLevelLabel.snp.makeConstraints { make in make.bottom.equalTo(profileNameLabel.snp.bottom) - make.trailing.equalTo(view.safeAreaLayoutGuide).inset(31) + make.leading.equalTo(profileNameLabel.snp.trailing).offset(10) + } + + followButton.snp.makeConstraints { make in + make.centerY.equalTo(profileNameLabel.snp.centerY) + make.trailing.equalTo(view.safeAreaLayoutGuide.snp.trailing).offset(-16) } firstHorizontalDivideLine.snp.makeConstraints { make in @@ -473,3 +620,46 @@ extension CourseDetailVC { } } } + +// CourseDetailVC.swift 파일 내에서 'CourseDetailVC' 클래스 안에 추가합니다. +extension CourseDetailVC { + func navigateToCourseView(with courseId: String) { + // courseId를 기반으로 원하는 뷰 컨트롤러로 이동하는 로직을 구현하세요. + // 예를 들어, courseId를 사용하여 특정 뷰 컨트롤러를 생성하고 데이터를 설정한 다음에 pushViewController를 사용하여 이동할 수 있습니다. + // 이동하는 방식은 여러 가지가 가능하므로 원하는 방식대로 구현하세요. + + // 예시 코드: + let destinationVC = CourseDetailVC() // 이 부분을 원하는 뷰 컨트롤러로 변경하세요. + destinationVC.courseId = Int(courseId) // 뷰 컨트롤러에 필요한 데이터 설정 + + // navigationController가 존재한다고 가정하고, pushViewController를 사용하여 이동합니다. + self.navigationController?.pushViewController(destinationVC, animated: true) + } +} + +#if DEBUG +import SwiftUI +struct Preview: UIViewControllerRepresentable { + + func makeUIViewController(context: Context) -> UIViewController { + // 이부분 + CourseDetailVC() + // 이거 보고싶은 현재 VC로 바꾸셈 + } + + func updateUIViewController(_ uiView: UIViewController, context: Context) { + // leave this empty + } +} + +struct ViewController_PreviewProvider: PreviewProvider { + static var previews: some View { + Group { + Preview() + .edgesIgnoringSafeArea(.all) + .previewDisplayName("Preview") + .previewDevice(PreviewDevice(rawValue: "iPhone 12 Pro")) + } + } +} +#endif diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/AdImageCollectionViewCell.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/AdImageCollectionViewCell.swift index 60140518..3d28022b 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/AdImageCollectionViewCell.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/AdImageCollectionViewCell.swift @@ -31,8 +31,7 @@ class AdImageCollectionViewCell: UICollectionViewCell, UIScrollViewDelegate { final let collectionViewInset = UIEdgeInsets(top: 28, left: 16, bottom: 28, right: 16) // MARK: - UI Components - var eventImg: UIImage = ImageLiterals.imgBanner4 - var imgBanners: [UIImage] = [ImageLiterals.imgBanner4, ImageLiterals.imgBanner1, ImageLiterals.imgBanner2] + var imgBanners: [UIImage] = [ImageLiterals.imgBanner1, ImageLiterals.imgBanner2] var currentPage: Int = 0 private var timer: Timer? diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/DepartureSearchVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/DepartureSearchVC.swift index d135000a..456156ff 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/DepartureSearchVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/DepartureSearchVC.swift @@ -27,6 +27,16 @@ final class DepartureSearchVC: UIViewController { $0.backgroundColor = .g5 } + private let selectNowButton = UIButton(type: .custom).then { + $0.setImage(ImageLiterals.icSelectNowButton, for: .normal) + $0.tintColor = .g1 + } + + private let selectMapButton = UIButton(type: .custom).then { + $0.setImage(ImageLiterals.icSelectMapButton, for: .normal) + $0.tintColor = .g3 + } + private let locationTableView = UITableView(frame: .zero, style: .plain).then { $0.backgroundColor = .white $0.separatorStyle = .none @@ -92,7 +102,7 @@ extension DepartureSearchVC { } private func setLayout() { - view.addSubviews(naviBar, dividerView, locationTableView) + view.addSubviews(naviBar, dividerView, locationTableView, selectNowButton, selectMapButton) naviBar.snp.makeConstraints { make in make.leading.top.trailing.equalTo(view.safeAreaLayoutGuide) @@ -105,6 +115,17 @@ extension DepartureSearchVC { make.height.equalTo(6) } + selectNowButton.snp.makeConstraints { make in + make.top.equalTo(dividerView.snp.bottom) + make.height.equalTo(40) + } + + selectMapButton.snp.makeConstraints { make in + make.top.equalTo(dividerView.snp.bottom) + make.leading.equalTo(selectNowButton.snp.trailing) + make.height.equalTo(40) + } + locationTableView.snp.makeConstraints { make in make.top.equalTo(dividerView.snp.bottom) make.leading.bottom.trailing.equalTo(view.safeAreaLayoutGuide) @@ -157,6 +178,9 @@ extension DepartureSearchVC: UITableViewDelegate, UITableViewDataSource { extension DepartureSearchVC: CustomNavigationBarDelegate { func searchButtonDidTap(text: String) { searchAddressWithKeyword(keyword: text) + // 검색 바 입력 시 버튼 숨김 처리 + selectNowButton.isHidden = true + selectMapButton.isHidden = true } } From f72cfd73070da86a17d8968dd904b2261a6a8d2c Mon Sep 17 00:00:00 2001 From: LeeMyeongJin Date: Fri, 1 Sep 2023 19:27:45 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[Feat]=20#178=20-=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20=EA=B3=B5=EC=9C=A0=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20[=EB=AF=B8=EC=99=84=EC=84=B1]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Runnect-iOS.xcodeproj/project.pbxproj | 4 +- .../Global/Supports/AppDelegate.swift | 1 - .../Global/Supports/SceneDelegate.swift | 56 ++++++++++++-- Runnect-iOS/Runnect-iOS/Info.plist | 12 ++- .../CourseDetail/VC/CourseDetailVC.swift | 76 ++++++------------- 5 files changed, 87 insertions(+), 62 deletions(-) diff --git a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj index 23d65d8e..85194f03 100644 --- a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj +++ b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj @@ -941,8 +941,8 @@ CE6655AF295D7FF600C64E12 /* Foundation */ = { isa = PBXGroup; children = ( - CE5875A1296015A2005D967E /* NetworkLoggerPlugin.swift */, CE40BB1D2968054F0030ABCA /* BaseResponse.swift */, + CE5875A1296015A2005D967E /* NetworkLoggerPlugin.swift */, CE40BB1F296805F70030ABCA /* NetworkResult.swift */, CE40BB21296806140030ABCA /* NetworkHelper.swift */, 712F661C2A7B7BAB00D9539B /* Config.swift */, @@ -1043,8 +1043,8 @@ CE9291262965D0ED0010959C /* StatsInfoView.swift */, CE6B63D729673450003F900F /* ListEmptyView.swift */, CEB0BCBB29D123350048CCD5 /* GuideView.swift */, - A3C2CAD629E53B2900EC525B /* RNAlertVC.swift */, CED791B22A2626AF001BFCFB /* ShadowView.swift */, + A3C2CAD629E53B2900EC525B /* RNAlertVC.swift */, ); path = UIComponents; sourceTree = ""; diff --git a/Runnect-iOS/Runnect-iOS/Global/Supports/AppDelegate.swift b/Runnect-iOS/Runnect-iOS/Global/Supports/AppDelegate.swift index 6da1fca6..e5414613 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Supports/AppDelegate.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Supports/AppDelegate.swift @@ -73,4 +73,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } } - diff --git a/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift b/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift index 5f92b293..f378d0ff 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift @@ -25,41 +25,87 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + + print("🔥 scene에서 뷰 동작 🔥") + if let url = URLContexts.first?.url { + + print("🔥 url : \(url)🔥 \n") + + if url.scheme == "kakao27d01e20b51e5925bf386a6c5465849f" { // 앱의 URL Scheme를 확인합니다. + + if let host = url.host, host == "kakaolink" { + // 딥링크 경로가 "detail"일 경우 CourseDetailView로 이동하도록 처리합니다. + if let courseIdString = url.queryParameters?["publicCourseId"], let courseId = Int(courseIdString) { + + print("🔥 url.queryParameters : \(url.queryParameters!)🔥 \n") + print("🔥 courseIdString : \(courseIdString)🔥 \n") + let courseDetailVC = CourseDetailVC() // 해당 뷰 컨트롤러 클래스를 생성합니다. +// courseDetailVC.courseId = courseId // CourseDetailView에 값을 전달합니다. + + // 이제 courseDetailVC를 현재 화면에 추가하거나 모달로 표시할 수 있습니다. + // 예를 들어, 현재의 루트 뷰 컨트롤러에 추가하는 경우: +// if let rootViewController = window?.rootViewController { +// rootViewController.addChild(courseDetailVC) +// rootViewController.view.addSubview(courseDetailVC.view) +// courseDetailVC.didMove(toParent: rootViewController) +// } + } + } + + } + if (AuthApi.isKakaoTalkLoginUrl(url)) { _ = AuthController.handleOpenUrl(url: url) } } + + } - + func sceneDidDisconnect(_ scene: UIScene) { // Called as the scene is being released by the system. // This occurs shortly after the scene enters the background, or when its session is discarded. // Release any resources associated with this scene that can be re-created the next time the scene connects. // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). } - + func sceneDidBecomeActive(_ scene: UIScene) { // Called when the scene has moved from an inactive state to an active state. // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. } - + func sceneWillResignActive(_ scene: UIScene) { // Called when the scene will move from an active state to an inactive state. // This may occur due to temporary interruptions (ex. an incoming phone call). } - + func sceneWillEnterForeground(_ scene: UIScene) { // Called as the scene transitions from the background to the foreground. // Use this method to undo the changes made on entering the background. } - + func sceneDidEnterBackground(_ scene: UIScene) { // Called as the scene transitions from the foreground to the background. // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. } + +} + +extension URL { + var queryParameters: [String: String]? { + guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true), + let queryItems = components.queryItems else { + return nil + } + var parameters = [String: String]() + for item in queryItems { + parameters[item.name] = item.value + } + return parameters + } } diff --git a/Runnect-iOS/Runnect-iOS/Info.plist b/Runnect-iOS/Runnect-iOS/Info.plist index 2c691327..6677f7ca 100644 --- a/Runnect-iOS/Runnect-iOS/Info.plist +++ b/Runnect-iOS/Runnect-iOS/Info.plist @@ -23,10 +23,20 @@ CFBundleTypeRole Editor + CFBundleURLName + CFBundleURLSchemes kakao27d01e20b51e5925bf386a6c5465849f - myapp + runnect + + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + runnect diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift index e9cac3e7..5698e772 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift @@ -34,7 +34,7 @@ final class CourseDetailVC: UIViewController { private var publicCourseId: Int? private var isMyCourse: Bool? - private var safariViewController : SFSafariViewController? + private var safariViewController: SFSafariViewController? // MARK: - UI Components @@ -203,54 +203,39 @@ extension CourseDetailVC { // // present(activityViewController, animated: true, completion: nil) // } - - @objc func shareButtonTapped() { + @objc func shareButtonTapped() { guard let model = self.uploadedCourseDetailModel else { return } let title = model.publicCourse.title let courseId = model.publicCourse.courseId + let description = model.publicCourse.description let courseImage = model.publicCourse.image - let courseDescription = model.publicCourse.description - let deepLinkURLString = "myapp://detail?courseId=\(courseId)" - // Send the feed message using KakaoLink API - if ShareApi.isKakaoTalkSharingAvailable() { - - // Web Link로 전송이 된다. 하지만 우리는 앱 링크를 받을거기 때문에 딱히 필요가 없으. - // 아래 줄을 주석해도 상관없다. - let link = Link(mobileWebUrl: URL(string: deepLinkURLString)) - - // 우리가 원하는 앱으로 보내주는 링크이다. - // second, vvv는 url 링크 마지막에 딸려서 오기 때문에, 이 파라미터를 바탕으로 파싱해서 - // 앱단에서 원하는 기능을 만들어서 실행할 수 있다 예를 들면 다른 뷰 페이지로 이동 등등~ - let appLink = Link(iosExecutionParams: ["key1": "courseId=\(courseId)"]) + // 딥 링크 URL 생성 + let deepLinkURLString = "kakao27d01e20b51e5925bf386a6c5465849f://kakaolink?publicCourseId=\(courseId)" + print("🔥내가 누른 코스 아이디 \(courseId) \n 🔥내가 만든 딥링크 확인 \(deepLinkURLString)") + // KakaoLink 템플릿을 생성. + let link = Link(mobileWebUrl: URL(string: deepLinkURLString)) + let appLink = Link(iosExecutionParams: ["publicCourseId": String(courseId)]) + let button = Button(title: "앱에서 보기", link: appLink) + let content = Content(title: title, + imageUrl: URL(string: courseImage)!, + description: description, + link: link) + let template = FeedTemplate(content: content, buttons: [button]) - // 해당 appLink를 들고 있을 버튼을 만들어준다. - let button = Button(title: "앱에서 보기", link: link) - - // Content는 이제 사진과 함께 글들이 적혀있다. - let content = Content(title: title, - imageUrl: URL(string: courseImage)!, - description: courseDescription, - link: appLink) - - // 템플릿에 버튼을 추가할때 아래 buttons에 배열의 형태로 넣어준다. - // 만약 버튼을 하나 더 추가하려면 버튼 변수를 만들고 [button, button2] 이런 식으로 진행하면 된다 . - let template = FeedTemplate(content: content, buttons: [button]) - - // 메시지 템플릿 encode + // 카카오톡으로 공유합니다. + if ShareApi.isKakaoTalkSharingAvailable() { if let templateJsonData = (try? SdkJSONEncoder.custom.encode(template)) { - - // 생성한 메시지 템플릿 객체를 jsonObject로 변환 if let templateJsonObject = SdkUtils.toJsonObject(templateJsonData) { - ShareApi.shared.shareDefault(templateObject: templateJsonObject) {(linkResult, error) in + ShareApi.shared.shareDefault(templateObject: templateJsonObject) { (linkResult, error) in if let error = error { - print("error : \(error)") + print("🔥카카오링크 공유 실패: error : \(error)🔥") } else { - print("defaultLink(templateObject:templateJsonObject) success.") + print("⭐️카카오 링크 공유 성공 success.⭐️") guard let linkResult = linkResult else { return } UIApplication.shared.open(linkResult.url, options: [:], completionHandler: nil) } @@ -258,7 +243,8 @@ extension CourseDetailVC { } } } else { - // 없을 경우 카카오톡 앱스토어로 이동합니다. (이거 하려면 URL Scheme에 itms-apps 추가 해야함) + // 카카오톡 앱이 없을 경우 근데 딥링크는 요거 아마 안됌.. Universial Link인가 그거해야함 + print("⭐️카카오톡 없어서 앱스토어 가야지?") let appBundleID = "com.runnect.Runnect-iOS" let appStoreURLString = "https://itunes.apple.com/app/id\(appBundleID)" if let url = URL(string: appStoreURLString), UIApplication.shared.canOpenURL(url) { @@ -396,7 +382,7 @@ extension CourseDetailVC { make.centerY.equalTo(navibar) } shareButton.snp.makeConstraints { make in - make.trailing.leading.equalTo(self.view.safeAreaLayoutGuide).offset(135) + make.trailing.trailing.equalTo(moreButton).offset(-50) make.centerY.equalTo(navibar) } } @@ -621,22 +607,6 @@ extension CourseDetailVC { } } -// CourseDetailVC.swift 파일 내에서 'CourseDetailVC' 클래스 안에 추가합니다. -extension CourseDetailVC { - func navigateToCourseView(with courseId: String) { - // courseId를 기반으로 원하는 뷰 컨트롤러로 이동하는 로직을 구현하세요. - // 예를 들어, courseId를 사용하여 특정 뷰 컨트롤러를 생성하고 데이터를 설정한 다음에 pushViewController를 사용하여 이동할 수 있습니다. - // 이동하는 방식은 여러 가지가 가능하므로 원하는 방식대로 구현하세요. - - // 예시 코드: - let destinationVC = CourseDetailVC() // 이 부분을 원하는 뷰 컨트롤러로 변경하세요. - destinationVC.courseId = Int(courseId) // 뷰 컨트롤러에 필요한 데이터 설정 - - // navigationController가 존재한다고 가정하고, pushViewController를 사용하여 이동합니다. - self.navigationController?.pushViewController(destinationVC, animated: true) - } -} - #if DEBUG import SwiftUI struct Preview: UIViewControllerRepresentable { From 28ec9f19011fc1eafb1895905b4b08b636f5b3a0 Mon Sep 17 00:00:00 2001 From: LeeMyeongJin Date: Fri, 1 Sep 2023 19:27:45 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[Fix]=20#178=20-=20=EC=BD=94=EC=8A=A4=20?= =?UTF-8?q?=EA=B3=B5=EC=9C=A0=20=EA=B8=B0=EB=8A=A5=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?firebase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Runnect-iOS.xcodeproj/project.pbxproj | 4 +- .../Global/Supports/AppDelegate.swift | 1 - .../Global/Supports/SceneDelegate.swift | 56 ++++++++++++-- Runnect-iOS/Runnect-iOS/Info.plist | 12 ++- .../CourseDetail/VC/CourseDetailVC.swift | 76 ++++++------------- 5 files changed, 87 insertions(+), 62 deletions(-) diff --git a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj index 23d65d8e..85194f03 100644 --- a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj +++ b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj @@ -941,8 +941,8 @@ CE6655AF295D7FF600C64E12 /* Foundation */ = { isa = PBXGroup; children = ( - CE5875A1296015A2005D967E /* NetworkLoggerPlugin.swift */, CE40BB1D2968054F0030ABCA /* BaseResponse.swift */, + CE5875A1296015A2005D967E /* NetworkLoggerPlugin.swift */, CE40BB1F296805F70030ABCA /* NetworkResult.swift */, CE40BB21296806140030ABCA /* NetworkHelper.swift */, 712F661C2A7B7BAB00D9539B /* Config.swift */, @@ -1043,8 +1043,8 @@ CE9291262965D0ED0010959C /* StatsInfoView.swift */, CE6B63D729673450003F900F /* ListEmptyView.swift */, CEB0BCBB29D123350048CCD5 /* GuideView.swift */, - A3C2CAD629E53B2900EC525B /* RNAlertVC.swift */, CED791B22A2626AF001BFCFB /* ShadowView.swift */, + A3C2CAD629E53B2900EC525B /* RNAlertVC.swift */, ); path = UIComponents; sourceTree = ""; diff --git a/Runnect-iOS/Runnect-iOS/Global/Supports/AppDelegate.swift b/Runnect-iOS/Runnect-iOS/Global/Supports/AppDelegate.swift index 6da1fca6..e5414613 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Supports/AppDelegate.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Supports/AppDelegate.swift @@ -73,4 +73,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } } - diff --git a/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift b/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift index 5f92b293..f378d0ff 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift @@ -25,41 +25,87 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + + print("🔥 scene에서 뷰 동작 🔥") + if let url = URLContexts.first?.url { + + print("🔥 url : \(url)🔥 \n") + + if url.scheme == "kakao27d01e20b51e5925bf386a6c5465849f" { // 앱의 URL Scheme를 확인합니다. + + if let host = url.host, host == "kakaolink" { + // 딥링크 경로가 "detail"일 경우 CourseDetailView로 이동하도록 처리합니다. + if let courseIdString = url.queryParameters?["publicCourseId"], let courseId = Int(courseIdString) { + + print("🔥 url.queryParameters : \(url.queryParameters!)🔥 \n") + print("🔥 courseIdString : \(courseIdString)🔥 \n") + let courseDetailVC = CourseDetailVC() // 해당 뷰 컨트롤러 클래스를 생성합니다. +// courseDetailVC.courseId = courseId // CourseDetailView에 값을 전달합니다. + + // 이제 courseDetailVC를 현재 화면에 추가하거나 모달로 표시할 수 있습니다. + // 예를 들어, 현재의 루트 뷰 컨트롤러에 추가하는 경우: +// if let rootViewController = window?.rootViewController { +// rootViewController.addChild(courseDetailVC) +// rootViewController.view.addSubview(courseDetailVC.view) +// courseDetailVC.didMove(toParent: rootViewController) +// } + } + } + + } + if (AuthApi.isKakaoTalkLoginUrl(url)) { _ = AuthController.handleOpenUrl(url: url) } } + + } - + func sceneDidDisconnect(_ scene: UIScene) { // Called as the scene is being released by the system. // This occurs shortly after the scene enters the background, or when its session is discarded. // Release any resources associated with this scene that can be re-created the next time the scene connects. // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). } - + func sceneDidBecomeActive(_ scene: UIScene) { // Called when the scene has moved from an inactive state to an active state. // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. } - + func sceneWillResignActive(_ scene: UIScene) { // Called when the scene will move from an active state to an inactive state. // This may occur due to temporary interruptions (ex. an incoming phone call). } - + func sceneWillEnterForeground(_ scene: UIScene) { // Called as the scene transitions from the background to the foreground. // Use this method to undo the changes made on entering the background. } - + func sceneDidEnterBackground(_ scene: UIScene) { // Called as the scene transitions from the foreground to the background. // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. } + +} + +extension URL { + var queryParameters: [String: String]? { + guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true), + let queryItems = components.queryItems else { + return nil + } + var parameters = [String: String]() + for item in queryItems { + parameters[item.name] = item.value + } + return parameters + } } diff --git a/Runnect-iOS/Runnect-iOS/Info.plist b/Runnect-iOS/Runnect-iOS/Info.plist index 2c691327..6677f7ca 100644 --- a/Runnect-iOS/Runnect-iOS/Info.plist +++ b/Runnect-iOS/Runnect-iOS/Info.plist @@ -23,10 +23,20 @@ CFBundleTypeRole Editor + CFBundleURLName + CFBundleURLSchemes kakao27d01e20b51e5925bf386a6c5465849f - myapp + runnect + + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + runnect diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift index e9cac3e7..5698e772 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift @@ -34,7 +34,7 @@ final class CourseDetailVC: UIViewController { private var publicCourseId: Int? private var isMyCourse: Bool? - private var safariViewController : SFSafariViewController? + private var safariViewController: SFSafariViewController? // MARK: - UI Components @@ -203,54 +203,39 @@ extension CourseDetailVC { // // present(activityViewController, animated: true, completion: nil) // } - - @objc func shareButtonTapped() { + @objc func shareButtonTapped() { guard let model = self.uploadedCourseDetailModel else { return } let title = model.publicCourse.title let courseId = model.publicCourse.courseId + let description = model.publicCourse.description let courseImage = model.publicCourse.image - let courseDescription = model.publicCourse.description - let deepLinkURLString = "myapp://detail?courseId=\(courseId)" - // Send the feed message using KakaoLink API - if ShareApi.isKakaoTalkSharingAvailable() { - - // Web Link로 전송이 된다. 하지만 우리는 앱 링크를 받을거기 때문에 딱히 필요가 없으. - // 아래 줄을 주석해도 상관없다. - let link = Link(mobileWebUrl: URL(string: deepLinkURLString)) - - // 우리가 원하는 앱으로 보내주는 링크이다. - // second, vvv는 url 링크 마지막에 딸려서 오기 때문에, 이 파라미터를 바탕으로 파싱해서 - // 앱단에서 원하는 기능을 만들어서 실행할 수 있다 예를 들면 다른 뷰 페이지로 이동 등등~ - let appLink = Link(iosExecutionParams: ["key1": "courseId=\(courseId)"]) + // 딥 링크 URL 생성 + let deepLinkURLString = "kakao27d01e20b51e5925bf386a6c5465849f://kakaolink?publicCourseId=\(courseId)" + print("🔥내가 누른 코스 아이디 \(courseId) \n 🔥내가 만든 딥링크 확인 \(deepLinkURLString)") + // KakaoLink 템플릿을 생성. + let link = Link(mobileWebUrl: URL(string: deepLinkURLString)) + let appLink = Link(iosExecutionParams: ["publicCourseId": String(courseId)]) + let button = Button(title: "앱에서 보기", link: appLink) + let content = Content(title: title, + imageUrl: URL(string: courseImage)!, + description: description, + link: link) + let template = FeedTemplate(content: content, buttons: [button]) - // 해당 appLink를 들고 있을 버튼을 만들어준다. - let button = Button(title: "앱에서 보기", link: link) - - // Content는 이제 사진과 함께 글들이 적혀있다. - let content = Content(title: title, - imageUrl: URL(string: courseImage)!, - description: courseDescription, - link: appLink) - - // 템플릿에 버튼을 추가할때 아래 buttons에 배열의 형태로 넣어준다. - // 만약 버튼을 하나 더 추가하려면 버튼 변수를 만들고 [button, button2] 이런 식으로 진행하면 된다 . - let template = FeedTemplate(content: content, buttons: [button]) - - // 메시지 템플릿 encode + // 카카오톡으로 공유합니다. + if ShareApi.isKakaoTalkSharingAvailable() { if let templateJsonData = (try? SdkJSONEncoder.custom.encode(template)) { - - // 생성한 메시지 템플릿 객체를 jsonObject로 변환 if let templateJsonObject = SdkUtils.toJsonObject(templateJsonData) { - ShareApi.shared.shareDefault(templateObject: templateJsonObject) {(linkResult, error) in + ShareApi.shared.shareDefault(templateObject: templateJsonObject) { (linkResult, error) in if let error = error { - print("error : \(error)") + print("🔥카카오링크 공유 실패: error : \(error)🔥") } else { - print("defaultLink(templateObject:templateJsonObject) success.") + print("⭐️카카오 링크 공유 성공 success.⭐️") guard let linkResult = linkResult else { return } UIApplication.shared.open(linkResult.url, options: [:], completionHandler: nil) } @@ -258,7 +243,8 @@ extension CourseDetailVC { } } } else { - // 없을 경우 카카오톡 앱스토어로 이동합니다. (이거 하려면 URL Scheme에 itms-apps 추가 해야함) + // 카카오톡 앱이 없을 경우 근데 딥링크는 요거 아마 안됌.. Universial Link인가 그거해야함 + print("⭐️카카오톡 없어서 앱스토어 가야지?") let appBundleID = "com.runnect.Runnect-iOS" let appStoreURLString = "https://itunes.apple.com/app/id\(appBundleID)" if let url = URL(string: appStoreURLString), UIApplication.shared.canOpenURL(url) { @@ -396,7 +382,7 @@ extension CourseDetailVC { make.centerY.equalTo(navibar) } shareButton.snp.makeConstraints { make in - make.trailing.leading.equalTo(self.view.safeAreaLayoutGuide).offset(135) + make.trailing.trailing.equalTo(moreButton).offset(-50) make.centerY.equalTo(navibar) } } @@ -621,22 +607,6 @@ extension CourseDetailVC { } } -// CourseDetailVC.swift 파일 내에서 'CourseDetailVC' 클래스 안에 추가합니다. -extension CourseDetailVC { - func navigateToCourseView(with courseId: String) { - // courseId를 기반으로 원하는 뷰 컨트롤러로 이동하는 로직을 구현하세요. - // 예를 들어, courseId를 사용하여 특정 뷰 컨트롤러를 생성하고 데이터를 설정한 다음에 pushViewController를 사용하여 이동할 수 있습니다. - // 이동하는 방식은 여러 가지가 가능하므로 원하는 방식대로 구현하세요. - - // 예시 코드: - let destinationVC = CourseDetailVC() // 이 부분을 원하는 뷰 컨트롤러로 변경하세요. - destinationVC.courseId = Int(courseId) // 뷰 컨트롤러에 필요한 데이터 설정 - - // navigationController가 존재한다고 가정하고, pushViewController를 사용하여 이동합니다. - self.navigationController?.pushViewController(destinationVC, animated: true) - } -} - #if DEBUG import SwiftUI struct Preview: UIViewControllerRepresentable { From 356d5600b4f66a4c4281daed5bdc2f91987ed443 Mon Sep 17 00:00:00 2001 From: LeeMyeongJin Date: Wed, 6 Sep 2023 13:57:05 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[Fix]=20#178=20-=20=EC=BD=94=EC=8A=A4=20?= =?UTF-8?q?=EA=B3=B5=EC=9C=A0=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?firebase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Runnect-iOS/Podfile | 1 + Runnect-iOS/Podfile.lock | 27 ++- .../Runnect-iOS.xcodeproj/project.pbxproj | 8 +- .../Global/Supports/AppDelegate.swift | 30 +--- .../Global/Supports/SceneDelegate.swift | 154 ++++++++++++----- .../UIComponents/CustomNavigationBar.swift | 5 +- Runnect-iOS/Runnect-iOS/Info.plist | 4 +- .../CourseDetail/VC/CourseDetailVC.swift | 156 ++++-------------- .../Runnect-iOS/Runnect-iOS.entitlements | 4 + 9 files changed, 197 insertions(+), 192 deletions(-) diff --git a/Runnect-iOS/Podfile b/Runnect-iOS/Podfile index 06d9b03f..c9ac53f9 100644 --- a/Runnect-iOS/Podfile +++ b/Runnect-iOS/Podfile @@ -15,6 +15,7 @@ target 'Runnect-iOS' do pod 'KakaoSDKUser' pod 'KakaoSDKShare' pod 'KakaoSDKTemplate' + pod 'FirebaseDynamicLinks' # Pods for Runnect-iOS diff --git a/Runnect-iOS/Podfile.lock b/Runnect-iOS/Podfile.lock index 24d58004..5b4d89c1 100644 --- a/Runnect-iOS/Podfile.lock +++ b/Runnect-iOS/Podfile.lock @@ -1,5 +1,18 @@ PODS: - Alamofire (5.7.1) + - FirebaseCore (10.14.0): + - FirebaseCoreInternal (~> 10.0) + - GoogleUtilities/Environment (~> 7.8) + - GoogleUtilities/Logger (~> 7.8) + - FirebaseCoreInternal (10.14.0): + - "GoogleUtilities/NSData+zlib (~> 7.8)" + - FirebaseDynamicLinks (10.14.0): + - FirebaseCore (~> 10.0) + - GoogleUtilities/Environment (7.11.5): + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/Logger (7.11.5): + - GoogleUtilities/Environment + - "GoogleUtilities/NSData+zlib (7.11.5)" - KakaoSDKAuth (2.16.0): - KakaoSDKCommon (= 2.16.0) - KakaoSDKCommon (2.16.0): @@ -24,10 +37,12 @@ PODS: - NMapsGeometry (1.0.1) - NMapsMap (3.17.0): - NMapsGeometry + - PromisesObjC (2.3.1) - SnapKit (5.6.0) - Then (3.0.0) DEPENDENCIES: + - FirebaseDynamicLinks - KakaoSDKAuth - KakaoSDKCommon - KakaoSDKShare @@ -42,6 +57,10 @@ DEPENDENCIES: SPEC REPOS: trunk: - Alamofire + - FirebaseCore + - FirebaseCoreInternal + - FirebaseDynamicLinks + - GoogleUtilities - KakaoSDKAuth - KakaoSDKCommon - KakaoSDKShare @@ -51,11 +70,16 @@ SPEC REPOS: - Moya - NMapsGeometry - NMapsMap + - PromisesObjC - SnapKit - Then SPEC CHECKSUMS: Alamofire: 0123a34370cb170936ae79a8df46cc62b2edeb88 + FirebaseCore: 6fc17ac9f03509d51c131298aacb3ee5698b4f02 + FirebaseCoreInternal: d558159ee6cc4b823c2296ecc193de9f6d9a5bb3 + FirebaseDynamicLinks: 0eaabff2d0e5d0e576c0227227b00771aa2f3aaf + GoogleUtilities: 13e2c67ede716b8741c7989e26893d151b2b2084 KakaoSDKAuth: 1b85ed7c41b0517bfd1fc9dc46c292c75b8cb610 KakaoSDKCommon: d6579aa2e9d963d74e13d741cbf1cce48b8b0c17 KakaoSDKShare: efc0415c4f33274232604eeaf96fb03641facdca @@ -65,9 +89,10 @@ SPEC CHECKSUMS: Moya: 138f0573e53411fb3dc17016add0b748dfbd78ee NMapsGeometry: 53c573ead66466681cf123f99f698dc8071a4b83 NMapsMap: a5b909a31b6f3d27a670f6eb2ddc913c38975474 + PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 SnapKit: e01d52ebb8ddbc333eefe2132acf85c8227d9c25 Then: 844265ae87834bbe1147d91d5d41a404da2ec27d -PODFILE CHECKSUM: 77a648fc451eb8403d22fda5ccf06419d697c921 +PODFILE CHECKSUM: 43e89e9e217761b44c0c7e93fb4abfab29a82ece COCOAPODS: 1.12.1 diff --git a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj index 85194f03..52db7cbc 100644 --- a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj +++ b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 0AEBD608F3973389E8E1C6D6 /* Pods_Runnect_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 015778D02D5CDE0838284CD7 /* Pods_Runnect_iOS.framework */; }; + 7110A6022AA33624009A7E99 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7110A6012AA33624009A7E99 /* GoogleService-Info.plist */; }; 712F661D2A7B7BAB00D9539B /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 712F661C2A7B7BAB00D9539B /* Config.swift */; }; A3305A97296EF58C000B1A10 /* GoalRewardInfoDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3305A96296EF58C000B1A10 /* GoalRewardInfoDto.swift */; }; A3BC2F2B2962C3D500198261 /* GoalRewardInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BC2F2A2962C3D500198261 /* GoalRewardInfoVC.swift */; }; @@ -163,6 +164,8 @@ /* Begin PBXFileReference section */ 015778D02D5CDE0838284CD7 /* Pods_Runnect_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runnect_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3C3033C911343B5C57EB68E7 /* Pods-Runnect-iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runnect-iOS.debug.xcconfig"; path = "Target Support Files/Pods-Runnect-iOS/Pods-Runnect-iOS.debug.xcconfig"; sourceTree = ""; }; + 7110A6012AA33624009A7E99 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 7110A6032AA337DD009A7E99 /* Runnect-iOSDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Runnect-iOSDebug.entitlements"; sourceTree = ""; }; 712F661C2A7B7BAB00D9539B /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; A3305A96296EF58C000B1A10 /* GoalRewardInfoDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoalRewardInfoDto.swift; sourceTree = ""; }; A3BC2F2A2962C3D500198261 /* GoalRewardInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoalRewardInfoVC.swift; sourceTree = ""; }; @@ -792,11 +795,13 @@ CE4545C7295D7AF4003201E1 /* Runnect-iOS */ = { isa = PBXGroup; children = ( + 7110A6032AA337DD009A7E99 /* Runnect-iOSDebug.entitlements */, A3E55BA529C8AB0A0000D85D /* Runnect-iOS.entitlements */, CE6655AA295D7FAE00C64E12 /* Global */, CE6655A9295D7FAA00C64E12 /* Network */, CE6655A8295D7F7D00C64E12 /* Presentation */, CE4545D6295D7AF5003201E1 /* Info.plist */, + 7110A6012AA33624009A7E99 /* GoogleService-Info.plist */, ); path = "Runnect-iOS"; sourceTree = ""; @@ -1246,6 +1251,7 @@ files = ( CE665615295D989A00C64E12 /* .swiftlint.yml in Resources */, CE17F0342961BEF800E1DED0 /* Pretendard-Bold.otf in Resources */, + 7110A6022AA33624009A7E99 /* GoogleService-Info.plist in Resources */, CE17F0352961BEF800E1DED0 /* Pretendard-SemiBold.otf in Resources */, CE17F0332961BEF800E1DED0 /* Pretendard-Medium.otf in Resources */, CE6655BF295D82E200C64E12 /* .gitkeep in Resources */, @@ -1601,7 +1607,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = "Runnect-iOS/Runnect-iOS.entitlements"; + CODE_SIGN_ENTITLEMENTS = "Runnect-iOS/Runnect-iOSDebug.entitlements"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; diff --git a/Runnect-iOS/Runnect-iOS/Global/Supports/AppDelegate.swift b/Runnect-iOS/Runnect-iOS/Global/Supports/AppDelegate.swift index e5414613..acef8872 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Supports/AppDelegate.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Supports/AppDelegate.swift @@ -10,6 +10,7 @@ import UIKit import NMapsMap import KakaoSDKAuth import KakaoSDKCommon +import FirebaseCore @main class AppDelegate: UIResponder, UIApplicationDelegate { @@ -17,10 +18,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + + print("🔥AppDelegate 의 openURLContexts 입니다 \n🔥") if let url = URLContexts.first?.url { + + if (AuthApi.isKakaoTalkLoginUrl(url)) { _ = AuthController.handleOpenUrl(url: url) } + + } } @@ -31,33 +38,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + FirebaseApp.configure() NMFAuthManager.shared().clientId = Config.naverMapClientId KakaoSDK.initSDK(appKey: Config.kakaoNativeAppKey) return true } - - // Handle deep linking - func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { - if let components = URLComponents(url: url, resolvingAgainstBaseURL: false), - let host = components.host, - let queryItems = components.queryItems { - - // Handle deep link URL here - if host == "detail" { - if let courseId = queryItems.first(where: { $0.name == "courseId" })?.value { - // Now you can navigate to your desired view controller based on the courseId - if let navigationController = window?.rootViewController as? UINavigationController, - let courseDetailVC = navigationController.viewControllers.first as? CourseDetailVC { - // Call a method in your CourseDetailVC to navigate to the desired view - courseDetailVC.navigateToCourseView(with: courseId) - } - } - } - } - return false - } - // MARK: UISceneSession Lifecycle diff --git a/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift b/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift index f378d0ff..87d9e543 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift @@ -8,13 +8,26 @@ import UIKit import KakaoSDKAuth import KakaoSDKCommon +import FirebaseDynamicLinks +import FirebaseCore +import FirebaseCoreInternal class SceneDelegate: UIResponder, UIWindowSceneDelegate { - + var window: UIWindow? - - + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + + print("🔥 scene에서 willConnectTo 동작 🔥") + guard let _ = (scene as? UIWindowScene) else { return } + + if let userActivity = connectionOptions.userActivities.first { + print("🔥 scene에서 userActivity 동작 🔥") + self.scene(scene, continue: userActivity) + } + + print("🔥 scene에서 SplashVC() 동작 🔥") guard let windowScene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: windowScene) @@ -22,47 +35,75 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window.rootViewController = nav self.window = window window.makeKeyAndVisible() + + } + + func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + if let incomingURL = userActivity.webpageURL { + let linkHandled = DynamicLinks.dynamicLinks() + .handleUniversalLink(incomingURL) { dynamicLink, error in + + + if let courseId = self.handleDynamicLink(dynamicLink) { + guard let _ = (scene as? UIWindowScene) else { return } + + if let windowScene = scene as? UIWindowScene { + let window = UIWindow(windowScene: windowScene) + + let rootVC = CourseDetailVC() + rootVC.setPublicCourseId(publicCourseId: Int(courseId)) + rootVC.getUploadedCourseDetail(courseId: Int(courseId)) + + // CourseDetailVC를 NavigationController로 감싸고, rootViewController로 설정합니다. + let navigationController = UINavigationController(rootViewController: rootVC) + window.rootViewController = navigationController + window.makeKeyAndVisible() + self.window = window + } + } + } + } } func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + print("🔥 SceneDelegate의 openURLContexts입니다~ 🔥") - print("🔥 scene에서 뷰 동작 🔥") + print(URLContexts) + print(URLContexts.first!) if let url = URLContexts.first?.url { - - print("🔥 url : \(url)🔥 \n") - - if url.scheme == "kakao27d01e20b51e5925bf386a6c5465849f" { // 앱의 URL Scheme를 확인합니다. - - if let host = url.host, host == "kakaolink" { - // 딥링크 경로가 "detail"일 경우 CourseDetailView로 이동하도록 처리합니다. - if let courseIdString = url.queryParameters?["publicCourseId"], let courseId = Int(courseIdString) { + // Firebase Dynamic Links를 사용하여 딥 링크를 처리합니다. + print("🔥 SceneDelegate의 url은 : \(url) 🔥") + let linkHandled = DynamicLinks.dynamicLinks().handleUniversalLink(url) { dynamicLink, error in + if let courseId = self.handleDynamicLink(dynamicLink) { + guard let _ = (scene as? UIWindowScene) else { return } + + if let windowScene = scene as? UIWindowScene { + let window = UIWindow(windowScene: windowScene) + window.overrideUserInterfaceStyle = .light - print("🔥 url.queryParameters : \(url.queryParameters!)🔥 \n") - print("🔥 courseIdString : \(courseIdString)🔥 \n") - let courseDetailVC = CourseDetailVC() // 해당 뷰 컨트롤러 클래스를 생성합니다. -// courseDetailVC.courseId = courseId // CourseDetailView에 값을 전달합니다. - - // 이제 courseDetailVC를 현재 화면에 추가하거나 모달로 표시할 수 있습니다. - // 예를 들어, 현재의 루트 뷰 컨트롤러에 추가하는 경우: -// if let rootViewController = window?.rootViewController { -// rootViewController.addChild(courseDetailVC) -// rootViewController.view.addSubview(courseDetailVC.view) -// courseDetailVC.didMove(toParent: rootViewController) -// } + // CourseDetailVC 인스턴스를 생성합니다. + let rootVC = CourseDetailVC() + rootVC.setPublicCourseId(publicCourseId: Int(courseId)) + + // CourseDetailVC를 NavigationController로 감싸고, rootViewController로 설정합니다. + let navigationController = UINavigationController(rootViewController: rootVC) + window.rootViewController = navigationController + window.makeKeyAndVisible() + self.window = window } } - } - - if (AuthApi.isKakaoTalkLoginUrl(url)) { + print("🔥 바인딩 유무 ", linkHandled, "🔥") + + // Kakao SDK가 처리해야 하는지 확인합니다. + if AuthApi.isKakaoTalkLoginUrl(url) { _ = AuthController.handleOpenUrl(url: url) } } - - } + func sceneDidDisconnect(_ scene: UIScene) { // Called as the scene is being released by the system. // This occurs shortly after the scene enters the background, or when its session is discarded. @@ -90,22 +131,53 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. } + + func handleDynamicLink(_ dynamicLink: DynamicLink?) -> String? { + if let dynamicLink = dynamicLink, let url = dynamicLink.url, + let components = URLComponents(url: url, resolvingAgainstBaseURL: false), + let queryItems = components.queryItems { + for item in queryItems { + if item.name == "courseId", let courseId = item.value { + // courseId를 사용하여 특정 뷰로 이동 + // 예: courseId를 기반으로 상세 화면을 열거나 특정 기능 수행 + print("🔥코스아이디가 제대로 여기까지 오는가!", courseId, "🔥") + return courseId + } + } + } + return nil + } } -extension URL { - var queryParameters: [String: String]? { - guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true), - let queryItems = components.queryItems else { - return nil - } +extension CourseDetailVC { - var parameters = [String: String]() - for item in queryItems { - parameters[item.name] = item.value + func getUploadedCourseDetail(courseId: Int?) { + guard let publicCourseId = courseId else { return } + LoadingIndicator.showLoading() + Providers.publicCourseProvider.request(.getUploadedCourseDetail(publicCourseId: publicCourseId)) { [weak self] response in + guard let self = self else { return } + LoadingIndicator.hideLoading() + switch response { + case .success(let result): + let status = result.statusCode + if 200..<300 ~= status { + do { + let responseDto = try result.map(BaseResponse.self) + guard let data = responseDto.data else { return } + self.setData(model: data) + } catch { + print(error.localizedDescription) + } + } + if status >= 400 { + print("400 error") + self.showNetworkFailureToast() + } + case .failure(let error): + print(error.localizedDescription) + self.showNetworkFailureToast() + } } - - return parameters } } - diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/CustomNavigationBar.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/CustomNavigationBar.swift index 4614e790..d6ad2fac 100644 --- a/Runnect-iOS/Runnect-iOS/Global/UIComponents/CustomNavigationBar.swift +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/CustomNavigationBar.swift @@ -70,7 +70,6 @@ extension CustomNavigationBar { } } - private func setAddTarget(_ type: NaviType) { self.naviType = type self.leftButton.addTarget(self, action: #selector(popToPreviousVC), for: .touchUpInside) @@ -317,7 +316,5 @@ extension CustomNavigationBar: UITextFieldDelegate { self.hideKeyboard() return true } - - - } + diff --git a/Runnect-iOS/Runnect-iOS/Info.plist b/Runnect-iOS/Runnect-iOS/Info.plist index 6677f7ca..636de799 100644 --- a/Runnect-iOS/Runnect-iOS/Info.plist +++ b/Runnect-iOS/Runnect-iOS/Info.plist @@ -34,9 +34,11 @@ CFBundleTypeRole Editor + CFBundleURLName + DynamicLink CFBundleURLSchemes - runnect + com.runnect.Runnect-iOS diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift index 5698e772..aa2e7f68 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift @@ -15,7 +15,9 @@ import SafariServices import KakaoSDKCommon import KakaoSDKShare import KakaoSDKTemplate - +import FirebaseCore +import FirebaseDynamicLinks + final class CourseDetailVC: UIViewController { // MARK: - Properties @@ -153,108 +155,41 @@ extension CourseDetailVC { followButton.isSelected.toggle() } -// @objc func shareButtonTapped() { -// guard let model = self.uploadedCourseDetailModel else { -// return -// } -// -// let courseImage = model.publicCourse.image -// let courseId = model.publicCourse.courseId -// let courseTitle = model.publicCourse.title -// let courseDescription = model.publicCourse.description -// -// // Create a deep link URL for your app -// let deepLinkURLString = "myapp://detail?courseId=\(courseId)" -// -// // 공유할 배열 생성 -// var itemsToShare: [Any] = [] -// -// // Add course description -//// itemsToShare.append(courseImage) -//// itemsToShare.append(courseTitle) -//// itemsToShare.append(courseDescription!) -// -// // Check if your app is installed -// if let deepLinkURL = URL(string: deepLinkURLString), -// UIApplication.shared.canOpenURL(deepLinkURL) { -// // Your app is installed, share the deep link -// itemsToShare.append(deepLinkURL) -// } else { -// // Your app is not installed, share the app store link -// let appBundleID = "com.runnect.Runnect-iOS" -// let appStoreURLString = "https://itunes.apple.com/app/id\(appBundleID)" -// -// if let appStoreURL = URL(string: appStoreURLString) { -// itemsToShare.append(appStoreURL) -// } -// } -// -// // Create an activity view controller -// let activityViewController = UIActivityViewController(activityItems: itemsToShare, applicationActivities: nil) -// -// // Remove the excludedActivityTypes array to include all options -// activityViewController.excludedActivityTypes = nil -// -// // Present the activity view controller -// if let popoverPresentationController = activityViewController.popoverPresentationController { -// popoverPresentationController.sourceView = self.view -// popoverPresentationController.sourceRect = self.shareButton.frame -// } -// -// present(activityViewController, animated: true, completion: nil) -// } - @objc func shareButtonTapped() { guard let model = self.uploadedCourseDetailModel else { return } - + let title = model.publicCourse.title - let courseId = model.publicCourse.courseId + let courseId = model.publicCourse.id // primaryKey let description = model.publicCourse.description let courseImage = model.publicCourse.image + + let dynamicLinksDomainURIPrefix = "https://runnect.page.link" + guard let link = URL(string: "\(dynamicLinksDomainURIPrefix)/?courseId=\(courseId)") else { return } + let linkBuilder = DynamicLinkComponents(link: link, domainURIPrefix: dynamicLinksDomainURIPrefix) + linkBuilder!.iOSParameters = DynamicLinkIOSParameters(bundleID: "com.runnect.Runnect-iOS") + linkBuilder!.iOSParameters?.appStoreID = "1663884202" + linkBuilder!.iOSParameters?.minimumAppVersion = "1.0.4" - // 딥 링크 URL 생성 - let deepLinkURLString = "kakao27d01e20b51e5925bf386a6c5465849f://kakaolink?publicCourseId=\(courseId)" - print("🔥내가 누른 코스 아이디 \(courseId) \n 🔥내가 만든 딥링크 확인 \(deepLinkURLString)") - // KakaoLink 템플릿을 생성. - let link = Link(mobileWebUrl: URL(string: deepLinkURLString)) - let appLink = Link(iosExecutionParams: ["publicCourseId": String(courseId)]) - let button = Button(title: "앱에서 보기", link: appLink) - let content = Content(title: title, - imageUrl: URL(string: courseImage)!, - description: description, - link: link) - let template = FeedTemplate(content: content, buttons: [button]) - - // 카카오톡으로 공유합니다. - if ShareApi.isKakaoTalkSharingAvailable() { - if let templateJsonData = (try? SdkJSONEncoder.custom.encode(template)) { - if let templateJsonObject = SdkUtils.toJsonObject(templateJsonData) { - ShareApi.shared.shareDefault(templateObject: templateJsonObject) { (linkResult, error) in - if let error = error { - print("🔥카카오링크 공유 실패: error : \(error)🔥") - } else { - print("⭐️카카오 링크 공유 성공 success.⭐️") - guard let linkResult = linkResult else { return } - UIApplication.shared.open(linkResult.url, options: [:], completionHandler: nil) - } - } - } - } - } else { - // 카카오톡 앱이 없을 경우 근데 딥링크는 요거 아마 안됌.. Universial Link인가 그거해야함 - print("⭐️카카오톡 없어서 앱스토어 가야지?") - let appBundleID = "com.runnect.Runnect-iOS" - let appStoreURLString = "https://itunes.apple.com/app/id\(appBundleID)" - if let url = URL(string: appStoreURLString), UIApplication.shared.canOpenURL(url) { - if #available(iOS 10.0, *) { - UIApplication.shared.open(url, options: [:], completionHandler: nil) - } else { - UIApplication.shared.openURL(url) - } - } - } + linkBuilder!.socialMetaTagParameters = DynamicLinkSocialMetaTagParameters() + linkBuilder!.socialMetaTagParameters?.imageURL = URL(string: courseImage) + linkBuilder!.socialMetaTagParameters?.title = title + linkBuilder!.socialMetaTagParameters?.descriptionText = description + + guard let longDynamicLink = linkBuilder!.url else { return } + print("The long URL is: \(longDynamicLink)") + + /// 짧은 Dynamic Link로 변환 + linkBuilder?.shorten(completion: { url, _, _ in + guard let url = url else { return } + print("The short URL is: \(url)") + }) + + let activityVC = UIActivityViewController(activityItems: [longDynamicLink.absoluteString], applicationActivities: nil) + activityVC.popoverPresentationController?.sourceView = self.view + self.present(activityVC, animated: true, completion: nil) + } @objc func startButtonDidTap() { @@ -330,6 +265,10 @@ extension CourseDetailVC { self.publicCourseId = publicCourseId } + func setPublicCourseId(publicCourseId: Int?) { + self.publicCourseId = publicCourseId + } + func setData(model: UploadedCourseDetailResponseDto) { self.uploadedCourseDetailModel = model self.mapImageView.setImage(with: model.publicCourse.image) @@ -606,30 +545,3 @@ extension CourseDetailVC { } } } - -#if DEBUG -import SwiftUI -struct Preview: UIViewControllerRepresentable { - - func makeUIViewController(context: Context) -> UIViewController { - // 이부분 - CourseDetailVC() - // 이거 보고싶은 현재 VC로 바꾸셈 - } - - func updateUIViewController(_ uiView: UIViewController, context: Context) { - // leave this empty - } -} - -struct ViewController_PreviewProvider: PreviewProvider { - static var previews: some View { - Group { - Preview() - .edgesIgnoringSafeArea(.all) - .previewDisplayName("Preview") - .previewDevice(PreviewDevice(rawValue: "iPhone 12 Pro")) - } - } -} -#endif diff --git a/Runnect-iOS/Runnect-iOS/Runnect-iOS.entitlements b/Runnect-iOS/Runnect-iOS/Runnect-iOS.entitlements index a812db50..043f0eb4 100644 --- a/Runnect-iOS/Runnect-iOS/Runnect-iOS.entitlements +++ b/Runnect-iOS/Runnect-iOS/Runnect-iOS.entitlements @@ -6,5 +6,9 @@ Default + com.apple.developer.associated-domains + + applinks:runnect.page.link + From 917876bb307e287f7f5397a30bc205b8d4a57f11 Mon Sep 17 00:00:00 2001 From: LeeMyeongJin Date: Wed, 6 Sep 2023 13:57:27 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[Fix]=20#178=20-=20=EC=BD=94=EC=8A=A4=20?= =?UTF-8?q?=EA=B3=B5=EC=9C=A0=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?firebase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Runnect-iOS/GoogleService-Info.plist | 34 +++++++++++++++++++ .../Runnect-iOS/Runnect-iOSDebug.entitlements | 14 ++++++++ 2 files changed, 48 insertions(+) create mode 100644 Runnect-iOS/Runnect-iOS/GoogleService-Info.plist create mode 100644 Runnect-iOS/Runnect-iOS/Runnect-iOSDebug.entitlements diff --git a/Runnect-iOS/Runnect-iOS/GoogleService-Info.plist b/Runnect-iOS/Runnect-iOS/GoogleService-Info.plist new file mode 100644 index 00000000..239c397a --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/GoogleService-Info.plist @@ -0,0 +1,34 @@ + + + + + CLIENT_ID + 772504823881-bp4sinq6869e5jaqv57r3pk2l5mmnefe.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.772504823881-bp4sinq6869e5jaqv57r3pk2l5mmnefe + API_KEY + AIzaSyAWjFUbR5CKoP8_V5a56aXON--0DkM1faw + GCM_SENDER_ID + 772504823881 + PLIST_VERSION + 1 + BUNDLE_ID + com.runnect.Runnect-iOS + PROJECT_ID + runnect-6c5e8 + STORAGE_BUCKET + runnect-6c5e8.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:772504823881:ios:e955e328c32c8a864b3798 + + \ No newline at end of file diff --git a/Runnect-iOS/Runnect-iOS/Runnect-iOSDebug.entitlements b/Runnect-iOS/Runnect-iOS/Runnect-iOSDebug.entitlements new file mode 100644 index 00000000..043f0eb4 --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Runnect-iOSDebug.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.developer.applesignin + + Default + + com.apple.developer.associated-domains + + applinks:runnect.page.link + + +