diff --git a/berkeley-mobile.xcodeproj/project.pbxproj b/berkeley-mobile.xcodeproj/project.pbxproj index 380b072ce..cb859ba07 100644 --- a/berkeley-mobile.xcodeproj/project.pbxproj +++ b/berkeley-mobile.xcodeproj/project.pbxproj @@ -27,7 +27,6 @@ 01CDBBF125CA6F58006B93BD /* RequestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CDBBF025CA6F58006B93BD /* RequestError.swift */; }; 01CDFF6A257C614900D9FBD6 /* Colors+Resource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CDFF69257C614900D9FBD6 /* Colors+Resource.swift */; }; 01D11B8E2504453B00BDF660 /* ScrollingStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D11B8D2504453B00BDF660 /* ScrollingStackView.swift */; }; - 01D11B902504560700BDF660 /* GymDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D11B8F2504560700BDF660 /* GymDetailViewController.swift */; }; 01D269932544D86C000377B4 /* Apercu Light.otf in Resources */ = {isa = PBXBuildFile; fileRef = 01D2698A2544D86B000377B4 /* Apercu Light.otf */; }; 01D269942544D86C000377B4 /* Apercu Italic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 01D2698B2544D86B000377B4 /* Apercu Italic.otf */; }; 01D269952544D86C000377B4 /* Apercu Light Italic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 01D2698C2544D86B000377B4 /* Apercu Light Italic.otf */; }; @@ -79,6 +78,9 @@ 13EA64CA2399CE5B00FD8E13 /* SearchItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA64C92399CE5B00FD8E13 /* SearchItem.swift */; }; 13EA64CD2399CEDA00FD8E13 /* Gym.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA64CC2399CEDA00FD8E13 /* Gym.swift */; }; 13EA64D02399D50C00FD8E13 /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA64CF2399D50C00FD8E13 /* DataManager.swift */; }; + 1D2013F22DC4684E00E636DF /* DescriptionCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2013F12DC4684E00E636DF /* DescriptionCard.swift */; }; + 1D2013F42DC4685E00E636DF /* CategoryOverviewCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2013F32DC4685E00E636DF /* CategoryOverviewCard.swift */; }; + 1D7DD6E02DC2EB5B00A6BBA7 /* GymDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D7DD6DF2DC2EB5B00A6BBA7 /* GymDetailView.swift */; }; 1DB006AD2D71C8D6001CC870 /* ResourcesSectionDropdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB006AC2D71C8C0001CC870 /* ResourcesSectionDropdown.swift */; }; 1DB88F6D2D94DF78007713F7 /* OpenTimesCardSwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB88F6C2D94DF78007713F7 /* OpenTimesCardSwiftUIView.swift */; }; 29061D41241C450E002BC9D9 /* HasLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29061D40241C450E002BC9D9 /* HasLocation.swift */; }; @@ -217,7 +219,6 @@ 01CDBBF025CA6F58006B93BD /* RequestError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestError.swift; sourceTree = ""; }; 01CDFF69257C614900D9FBD6 /* Colors+Resource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Colors+Resource.swift"; sourceTree = ""; }; 01D11B8D2504453B00BDF660 /* ScrollingStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollingStackView.swift; sourceTree = ""; }; - 01D11B8F2504560700BDF660 /* GymDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GymDetailViewController.swift; sourceTree = ""; }; 01D2698A2544D86B000377B4 /* Apercu Light.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Apercu Light.otf"; sourceTree = ""; }; 01D2698B2544D86B000377B4 /* Apercu Italic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Apercu Italic.otf"; sourceTree = ""; }; 01D2698C2544D86B000377B4 /* Apercu Light Italic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Apercu Light Italic.otf"; sourceTree = ""; }; @@ -271,6 +272,9 @@ 13EA64C92399CE5B00FD8E13 /* SearchItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchItem.swift; sourceTree = ""; }; 13EA64CC2399CEDA00FD8E13 /* Gym.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Gym.swift; sourceTree = ""; }; 13EA64CF2399D50C00FD8E13 /* DataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = ""; }; + 1D2013F12DC4684E00E636DF /* DescriptionCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DescriptionCard.swift; sourceTree = ""; }; + 1D2013F32DC4685E00E636DF /* CategoryOverviewCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryOverviewCard.swift; sourceTree = ""; }; + 1D7DD6DF2DC2EB5B00A6BBA7 /* GymDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GymDetailView.swift; sourceTree = ""; }; 1DB006AC2D71C8C0001CC870 /* ResourcesSectionDropdown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourcesSectionDropdown.swift; sourceTree = ""; }; 1DB88F6C2D94DF78007713F7 /* OpenTimesCardSwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenTimesCardSwiftUIView.swift; sourceTree = ""; }; 29061D40241C450E002BC9D9 /* HasLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HasLocation.swift; sourceTree = ""; }; @@ -521,12 +525,12 @@ children = ( 13EA64C62399CDF900FD8E13 /* GymDataSource */, 1336A31D241C400F00949F32 /* GymClassDataSource */, - 01D11B8F2504560700BDF660 /* GymDetailViewController.swift */, 13E25DFC238C949E00B670B5 /* FitnessViewController.swift */, 135D7F78243AA6B1003F8BD1 /* Fitness+Controllers */, E83B6DA42D7A85D500AA9422 /* GymOccupancyScrapper.swift */, E83B6DA62D7A85F200AA9422 /* GymOccupancyView.swift */, E83B6DA82D7A860E00AA9422 /* GymOccupancyViewModel.swift */, + 1D7DD6DF2DC2EB5B00A6BBA7 /* GymDetailView.swift */, ); path = Fitness; sourceTree = ""; @@ -555,6 +559,8 @@ 1396013123865E2E005E4788 /* CardView.swift */, 298EE26F25BB82A2002BAF0F /* CardTableViewCell.swift */, 1396013223865E2E005E4788 /* TagView.swift */, + 1D2013F32DC4685E00E636DF /* CategoryOverviewCard.swift */, + 1D2013F12DC4684E00E636DF /* DescriptionCard.swift */, 018B982525327358004C3B26 /* ActionButton.swift */, 01B250272516AD4F00CBA459 /* IconPairView.swift */, 554CB99F23F3A83F00BB1715 /* EventTableViewCell.swift */, @@ -1176,9 +1182,11 @@ 13135BB3241244370056B169 /* FilterViewCell.swift in Sources */, 016A56D42519E96800531A12 /* CampusCalendarViewController.swift in Sources */, 298EE27025BB82A2002BAF0F /* CardTableViewCell.swift in Sources */, + 1D2013F22DC4684E00E636DF /* DescriptionCard.swift in Sources */, 13580AF62437D7E700D309AA /* LibraryDetailViewController.swift in Sources */, 135D7F77243A9BD1003F8BD1 /* Colors+GymClass.swift in Sources */, 55DCF7A123724835001B01B8 /* MaterialButton.swift in Sources */, + 1D2013F42DC4685E00E636DF /* CategoryOverviewCard.swift in Sources */, 297678892426D2C500FDD1EB /* SearchDrawerViewController.swift in Sources */, 29CB280A2404DD71009A2CFB /* FilterTableViewCell.swift in Sources */, 55DCF79623723CF2001B01B8 /* UIView+Extensions.swift in Sources */, @@ -1187,6 +1195,7 @@ 01D2699D2544E005000377B4 /* AcademicCalendarViewController.swift in Sources */, 1336A31C241C400800949F32 /* GymClass.swift in Sources */, E8B5975F2CA62D6F006DFBD5 /* SegmentedControlView.swift in Sources */, + 1D7DD6E02DC2EB5B00A6BBA7 /* GymDetailView.swift in Sources */, 13EA64C82399CE0800FD8E13 /* GymDataSource.swift in Sources */, 2E1C227D2D835A9D0021803C /* SearchBarView.swift in Sources */, 298EE26A25BB6C33002BAF0F /* Colors+StudyPact.swift in Sources */, @@ -1235,7 +1244,6 @@ 017C0B26251018BA00BFA80A /* Colors+MapMarker.swift in Sources */, E83B6DA92D7A860E00AA9422 /* GymOccupancyViewModel.swift in Sources */, 29345E2724A7E76300859A88 /* OverviewCardView.swift in Sources */, - 01D11B902504560700BDF660 /* GymDetailViewController.swift in Sources */, 55AF442D2453ACE600F13232 /* DiningLocation.swift in Sources */, 1336A329241DA56100949F32 /* DiningHallDataSource.swift in Sources */, 136DC97B2398B4D1009B1810 /* UIViewController+Extensions.swift in Sources */, diff --git a/berkeley-mobile/Common/CategoryOverviewCard.swift b/berkeley-mobile/Common/CategoryOverviewCard.swift new file mode 100644 index 000000000..922c768ad --- /dev/null +++ b/berkeley-mobile/Common/CategoryOverviewCard.swift @@ -0,0 +1,131 @@ +// +// CategoryOverviewCard.swift +// berkeley-mobile +// +// Created by Yihang Chen on 5/1/25. +// Copyright © 2025 ASUC OCTO. All rights reserved. +// + +import SwiftUI + +struct BMContactInfoRow: View { + let iconName: String + let text: String + var lineLimit: Int? = 1 + + var body: some View { + HStack(alignment: .top, spacing: 10) { + Image(systemName: iconName) + .frame(width: 18, height: 18) + + Text(text) + .font(Font(BMFont.light(12))) + .lineLimit(lineLimit) + .fixedSize(horizontal: false, vertical: true) + } + } +} + +struct BMCategoryOverviewCard: View { + let category: SearchItem & HasLocation & HasImage + + var body: some View { + HStack(alignment: .top, spacing: 16) { + VStack(alignment: .leading) { + titleView + + Spacer(minLength: 20) + + contactInfoView + } + + Spacer() + + imageView + } + .padding(12) + .background(Color(BMColor.cardBackground)) + .cornerRadius(12) + .shadow(color: Color(uiColor: .label).opacity(0.15), radius: 5, x: 0, y: 0) + .padding(.vertical, 8) + .padding(.horizontal, 4) + } + + private var titleView: some View { + Text(category.name) + .font(Font(BMFont.bold(23))) + .foregroundColor(Color(BMColor.blackText)) + .lineLimit(3) + .padding(.top, 8) + } + + private var contactInfoView: some View { + VStack(alignment: .leading, spacing: 12) { + if let address = category.address, !address.isEmpty { + BMContactInfoRow( + iconName: "location.fill", + text: address, + lineLimit: nil + ) + } + + if let hasPhone = category as? HasPhoneNumber, + let phoneNumber = hasPhone.phoneNumber, + !phoneNumber.isEmpty { + BMContactInfoRow( + iconName: "phone.fill", + text: phoneNumber + ) + } + + if let distance = category.distanceToUser { + BMContactInfoRow( + iconName: "figure.walk", + text: String(format: "%.1f miles", distance) + ) + } + } + .foregroundColor(Color(BMColor.blackText)) + } + + private var imageView: some View { + Group { + if let imageURL = category.imageURL { + AsyncImage(url: imageURL) { phase in + switch phase { + case .empty: + defaultImage + case .success(let image): + image + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 120, height: 200) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Color(uiColor: .systemGray4), lineWidth: 0.5) + ) + case .failure: + defaultImage + @unknown default: + EmptyView() + } + } + } else { + defaultImage + } + } + } + + private var defaultImage: some View { + Image(uiImage: Constants.doeGladeImage) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 120, height: 200) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Color(uiColor: .systemGray4), lineWidth: 0.5) + ) + } +} \ No newline at end of file diff --git a/berkeley-mobile/Common/DescriptionCard.swift b/berkeley-mobile/Common/DescriptionCard.swift new file mode 100644 index 000000000..8936ceb60 --- /dev/null +++ b/berkeley-mobile/Common/DescriptionCard.swift @@ -0,0 +1,29 @@ +// +// DescriptionCard.swift +// berkeley-mobile +// +// Created by Yihang Chen on 5/1/25. +// Copyright © 2025 ASUC OCTO. All rights reserved. +// + +import SwiftUI + +struct BMDescriptionCard: View { + let description: String + + var body: some View { + VStack(alignment: .leading, spacing: 10) { + Text("Description") + .font(Font(BMFont.bold(16))) + .foregroundColor(Color(BMColor.blackText)) + + Text(description) + .font(Font(BMFont.light(12))) + .foregroundColor(Color(BMColor.blackText)) + } + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .background(Color(BMColor.cardBackground)) + .cornerRadius(12) + } +} \ No newline at end of file diff --git a/berkeley-mobile/Fitness/GymDataSource/Gym.swift b/berkeley-mobile/Fitness/GymDataSource/Gym.swift index 7168301c1..85d786f6e 100644 --- a/berkeley-mobile/Fitness/GymDataSource/Gym.swift +++ b/berkeley-mobile/Fitness/GymDataSource/Gym.swift @@ -62,9 +62,20 @@ class Gym: SearchItem, HasLocation, CanFavorite, HasPhoneNumber, HasImage, HasOp self.phoneNumber = phoneNumber self.weeklyHours = weeklyHours self.name = name.trimmingCharacters(in: .whitespacesAndNewlines) - self.imageURL = URL(string: imageLink ?? "") + + if let imgLink = imageLink, !imgLink.isEmpty, let url = URL(string: imgLink) { + self.imageURL = url + } else { + self.imageURL = nil + } + self.icon = UIImage(named: "Walk")?.colored(BMColor.blackText) - self.website = URL(string: link ?? "") + + if let webLink = link, !webLink.isEmpty, let url = URL(string: webLink) { + self.website = url + } else { + self.website = nil + } } } diff --git a/berkeley-mobile/Fitness/GymDetailView.swift b/berkeley-mobile/Fitness/GymDetailView.swift new file mode 100644 index 000000000..608bfdaed --- /dev/null +++ b/berkeley-mobile/Fitness/GymDetailView.swift @@ -0,0 +1,181 @@ +// +// GymDetailView.swift +// +// Created by Yihang Chen on 4/9/25. +// Copyright © 2025 ASUC OCTO. All rights reserved. +// + +import SwiftUI + +struct ContactInfoRow: View { + let iconName: String + let text: String + var lineLimit: Int? = 1 + + var body: some View { + HStack(alignment: .top, spacing: 10) { + Image(systemName: iconName) + .frame(width: 18, height: 18) + + Text(text) + .font(Font(BMFont.light(12))) + .lineLimit(lineLimit) + .fixedSize(horizontal: false, vertical: true) + } + } +} + +struct CategoryOverviewCard: View { + let category: SearchItem & HasLocation & HasImage + + var body: some View { + HStack(alignment: .top, spacing: 16) { + VStack(alignment: .leading) { + titleView + + Spacer(minLength: 20) + + contactInfoView + } + + Spacer() + + imageView + } + .padding(12) + .background(Color(BMColor.cardBackground)) + .cornerRadius(12) + .shadow(color: Color(uiColor: .label).opacity(0.15), radius: 5, x: 0, y: 0) + .padding(.vertical, 8) + .padding(.horizontal, 4) + } + + private var titleView: some View { + Text(category.name) + .font(Font(BMFont.bold(23))) + .foregroundColor(Color(BMColor.blackText)) + .lineLimit(3) + .padding(.top, 8) + } + + private var contactInfoView: some View { + VStack(alignment: .leading, spacing: 12) { + if let address = category.address, !address.isEmpty { + ContactInfoRow( + iconName: "location.fill", + text: address, + lineLimit: nil + ) + } + + if let hasPhone = category as? HasPhoneNumber, + let phoneNumber = hasPhone.phoneNumber, + !phoneNumber.isEmpty { + ContactInfoRow( + iconName: "phone.fill", + text: phoneNumber + ) + } + + if let distance = category.distanceToUser { + ContactInfoRow( + iconName: "figure.walk", + text: String(format: "%.1f miles", distance) + ) + } + } + .foregroundColor(Color(BMColor.blackText)) + } + + private var imageView: some View { + Group { + if let imageURL = category.imageURL { + AsyncImage(url: imageURL) { phase in + switch phase { + case .empty: + defaultImage + case .success(let image): + image + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 120, height: 200) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Color(uiColor: .systemGray4), lineWidth: 0.5) + ) + case .failure: + defaultImage + @unknown default: + EmptyView() + } + } + } else { + defaultImage + } + } + } + + private var defaultImage: some View { + Image(uiImage: Constants.doeGladeImage) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 120, height: 200) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Color(uiColor: .systemGray4), lineWidth: 0.5) + ) + } +} + +struct GymDetailView: View { + @Environment(\.openURL) private var openURL + + let gym: Gym + init(gym: Gym) { + self.gym = gym + } + + var body: some View { + ScrollView { + VStack(spacing: 16) { + BMCategoryOverviewCard(category: gym) + + if gym.weeklyHours != nil { + OpenTimesCardSwiftUIView(item: gym) + } + + if let website = gym.website { + BMActionButton(title: "Learn More") { + openURL(website) + } + } + + if let description = gym.description, !description.isEmpty { + BMDescriptionCard(description: description) + } + } + .padding(.horizontal, 16) + .padding(.vertical, 5) + } + .navigationTitle(gym.name) + } +} + +#Preview { + let sampleGym = Gym( + name: "RSF (Recreational Sports Facility)", + description: "The Recreational Sports Facility (RSF) is UC Berkeley's largest fitness center, offering state-of-the-art equipment, group exercise classes, and various sports courts. Located at the heart of campus, it provides comprehensive fitness options for students and faculty.", + address: "2301 Bancroft Way, Berkeley, CA 94720", + phoneNumber: "(510) 111-2222", + imageLink: nil, + weeklyHours: nil, + link: "https://recsports.berkeley.edu/rsf/" + ) + + sampleGym.latitude = 37.8687 + sampleGym.longitude = -122.2614 + + return GymDetailView(gym: sampleGym) +} diff --git a/berkeley-mobile/Fitness/GymDetailViewController.swift b/berkeley-mobile/Fitness/GymDetailViewController.swift deleted file mode 100644 index dd9c3c053..000000000 --- a/berkeley-mobile/Fitness/GymDetailViewController.swift +++ /dev/null @@ -1,133 +0,0 @@ -// -// GymDetailViewController.swift -// bm-persona -// -// Created by Kevin Hu on 9/5/20. -// Copyright © 2020 RJ Pimentel. All rights reserved. -// - -import Firebase -import UIKit -import SwiftUI - - -// MARK: - GymDetailView - -struct GymDetailView: UIViewControllerRepresentable { - typealias UIViewControllerType = GymDetailViewController - - private let gym: Gym - - init(gym: Gym) { - self.gym = gym - } - - func makeUIViewController(context: Context) -> GymDetailViewController { - let gymDetailVC = GymDetailViewController() - gymDetailVC.gym = gym - return gymDetailVC - } - - func updateUIViewController(_ uiViewController: GymDetailViewController, context: Context) {} -} - - -// MARK: - GymDetailViewController - -fileprivate let kViewMargin: CGFloat = 16 - -class GymDetailViewController: UIViewController { - - var gym: Gym! - - var overviewCard: OverviewCardView! - var openTimesCard: OpenTimesCardView? - var occupancyCard: OccupancyGraphCardView? - var moreButton: ActionButton? - var descriptionCard: DescriptionCardView? - - override func viewDidLoad() { - super.viewDidLoad() - - setUpScrollView() - setUpOverviewCard() - setUpOpenTimesCard() - setUpoccupancyCard() - setupMoreButton() - setupDescriptionCard() - } - - /// Opens `gym.website` in Safari. Called as a result of tapping on `moreButton`. - @objc private func moreButtonClicked(sender: UIButton) { - guard let url = gym.website else { return } - UIApplication.shared.open(url, options: [:]) - } - - var scrollingStackView: ScrollingStackView = { - let scrollingStackView = ScrollingStackView() - scrollingStackView.scrollView.showsVerticalScrollIndicator = false - scrollingStackView.stackView.spacing = kViewMargin - return scrollingStackView - }() -} - -// MARK: - View - -extension GymDetailViewController { - - func setUpOverviewCard() { - overviewCard = OverviewCardView(item: gym, excludedElements: [.openTimes, .occupancy]) - overviewCard.heightAnchor.constraint(equalToConstant: 200).isActive = true - scrollingStackView.stackView.addArrangedSubview(overviewCard) - } - - func setUpOpenTimesCard() { - guard gym.weeklyHours != nil else { return } - openTimesCard = OpenTimesCardView(item: gym, animationView: scrollingStackView, toggleAction: { _ in - }) - guard let openTimesCard = self.openTimesCard else { return } - scrollingStackView.stackView.addArrangedSubview(openTimesCard) - } - - func setUpoccupancyCard() { - guard let occupancy = gym.occupancy, let forDay = occupancy.occupancy(for: DayOfWeek.weekday(Date())), forDay.count > 0 else { return } - occupancyCard = OccupancyGraphCardView(occupancy: occupancy, isOpen: gym.isOpen) - guard let occupancyCard = self.occupancyCard else { return } - scrollingStackView.stackView.addArrangedSubview(occupancyCard) - } - - - - func setupMoreButton() { - guard gym.website != nil else { return } - let button = ActionButton(title: "Learn More") - button.addTarget(self, action: #selector(moreButtonClicked), for: .touchUpInside) - scrollingStackView.stackView.addArrangedSubview(button) - moreButton = button - } - - func setupDescriptionCard() { - descriptionCard = DescriptionCardView(description: gym.description) - guard let descriptionCard = descriptionCard else { return } - scrollingStackView.stackView.addArrangedSubview(descriptionCard) - } - - func setUpScrollView() { - scrollingStackView.scrollView.setupDummyGesture() - view.addSubview(scrollingStackView) - scrollingStackView.setLayoutMargins(UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16)) - scrollingStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: kViewMargin).isActive = true - scrollingStackView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true - scrollingStackView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true - scrollingStackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true - } -} - -// MARK: - Analytics - -extension GymDetailViewController { - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - Analytics.logEvent("opened_gym", parameters: ["gym" : gym.name]) - } -}