├── CocoaHeadsNL ├── .gitattributes ├── .xctool-args ├── CocoaHeadsNL │ ├── Images.xcassets │ │ ├── Contents.json │ │ ├── Banner.imageset │ │ │ ├── Banner@1x.png │ │ │ ├── Banner@2x.png │ │ │ ├── Banner@3x.png │ │ │ ├── BannerDark@1x.png │ │ │ ├── BannerDark@2x.png │ │ │ ├── BannerDark@3x.png │ │ │ └── Contents.json │ │ ├── AboutTabIcon.imageset │ │ │ ├── about-1.png │ │ │ ├── about-2.png │ │ │ ├── about-3.png │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── AppIcon-120.png │ │ │ ├── AppIcon-152.png │ │ │ ├── AppIcon-167.png │ │ │ ├── AppIcon-176.png │ │ │ ├── AppIcon-180.png │ │ │ ├── AppIcon-196.png │ │ │ ├── AppIcon-29.png │ │ │ ├── AppIcon-40.png │ │ │ ├── AppIcon-48.png │ │ │ ├── AppIcon-55.png │ │ │ ├── AppIcon-58.png │ │ │ ├── AppIcon-76.png │ │ │ ├── AppIcon-80.png │ │ │ ├── AppIcon-87.png │ │ │ ├── AppIcon-120-1.png │ │ │ ├── AppIcon-58-1.png │ │ │ ├── AppIcon-58-2.png │ │ │ ├── AppIcon-80-1.png │ │ │ ├── AppIcon-80-2.png │ │ │ ├── AppIcon-87-1.png │ │ │ ├── iTunesArtwork@2x.png │ │ │ └── Contents.json │ │ ├── JobsTabIcon.imageset │ │ │ ├── JobsTabIcon.png │ │ │ ├── JobsTabIcon@2x.png │ │ │ ├── JobsTabIcon@3x.png │ │ │ └── Contents.json │ │ ├── CocoaHeadsNLLogo.imageset │ │ │ ├── CocoaHeadsNL.pdf │ │ │ ├── CocoaHeadsNL-1.pdf │ │ │ └── Contents.json │ │ ├── EventsTabIcon.imageset │ │ │ ├── EventsTabIcon.png │ │ │ ├── EventsTabIcon@2x.png │ │ │ ├── EventsTabIcon@3x.png │ │ │ └── Contents.json │ │ ├── CompaniesTabIcon.imageset │ │ │ ├── CompaniesTabIcon.png │ │ │ ├── CompaniesTabIconStroked@2x.png │ │ │ ├── CompaniesTabIconStroked@3x.png │ │ │ └── Contents.json │ │ ├── MeetupPlaceholder.imageset │ │ │ ├── MeetupPlaceholder.png │ │ │ ├── MeetupPlaceholder@2x.png │ │ │ ├── MeetupPlaceholder@3x.png │ │ │ └── Contents.json │ │ ├── CocoaHeadsNLLogoOnlyLight.imageset │ │ │ ├── CocoaHeadsNL.pdf │ │ │ └── Contents.json │ │ ├── DayLabelColorHistory.colorset │ │ │ └── Contents.json │ │ ├── DayLabelColor.colorset │ │ │ └── Contents.json │ │ ├── DayBackgroundViewColor.colorset │ │ │ └── Contents.json │ │ ├── DayBackgroundViewColorHistory.colorset │ │ │ └── Contents.json │ │ └── DayBackgroundViewColorToday.colorset │ │ │ └── Contents.json │ ├── CocoaHeadsNL500.png │ ├── CocoaHeadsNL-Bridging-Header.h │ ├── LogoCell.swift │ ├── MainViewContainer.swift │ ├── UIColor+Extensions.swift │ ├── Job+CoreDataClass.swift │ ├── TabBarController.swift │ ├── Meetup+CoreDataClass.swift │ ├── Company+CoreDataClass.swift │ ├── ParseConfig-template.plist │ ├── Contributor+CoreDataClass.swift │ ├── NSLocalizedString+Convenience.swift │ ├── MapAnnotation.swift │ ├── UINavigationItem+Banner.swift │ ├── UIImage+Resize.swift │ ├── OnlineCell.swift │ ├── Identifiable.swift │ ├── NSAttributedStringExtension.swift │ ├── Contributor+CoreDataProperties.swift │ ├── Job+CoreDataProperties.swift │ ├── CoreDataStack.swift │ ├── JobDataSource.swift │ ├── CocoaHeadsNL.entitlements │ ├── Company+CoreDataProperties.swift │ ├── HTMLDataCell.swift │ ├── UIAlertController+FetchErrors.swift │ ├── RequestReview.swift │ ├── TitleCell.swift │ ├── Meetup+CoreDataProperties.swift │ ├── ButtonCell.swift │ ├── CoreLocationController.swift │ ├── JobsCell.swift │ ├── CompanyDataSource.swift │ ├── NSManagedObjectContext+AsyncHelpers.swift │ ├── MapViewCell.swift │ ├── MeetupDataSource.swift │ ├── UITableView+Extensions.swift │ ├── CalendarTabImage.swift │ ├── DetailDataSource.swift │ ├── SplitViewController.swift │ ├── Launch Screen.storyboard │ ├── MeetupCell.swift │ ├── Info.plist │ ├── NSManagedObjectContext+SaveHelpers.swift │ ├── Model.xcdatamodeld │ │ └── Model.xcdatamodel │ │ │ └── contents │ ├── NSManagedObject+FetchHelpers.swift │ ├── AppDelegate.swift │ ├── DetailViewController.swift │ └── JobsCell.xib ├── CocoaHeadsNL.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── CocoaHeadsNL.xccheckout │ └── xcshareddata │ │ └── xcschemes │ │ ├── generalNotification.xcscheme │ │ ├── ItemServiceExtension.xcscheme │ │ ├── itemNotification.xcscheme │ │ └── CocoaHeadsNL.xcscheme ├── itemNotification │ ├── itemNotification.entitlements │ ├── Info.plist │ ├── NotificationViewController.swift │ └── Base.lproj │ │ └── MainInterface.storyboard ├── generalNotification │ ├── generalNotification.entitlements │ ├── NotificationViewController.swift │ ├── Info.plist │ └── Base.lproj │ │ └── MainInterface.storyboard ├── ItemServiceExtension │ ├── ItemServiceExtension.entitlements │ ├── Info.plist │ └── NotificationService.swift ├── .gitignore ├── CloudKitSync │ ├── package.json │ ├── config.js.example │ ├── jobs │ │ ├── loadEventInfo.js │ │ ├── loadJobInfo.js │ │ ├── loadContributorInfo.js │ │ └── xmlreader.js │ └── README.md ├── CocoaHeadsNLUITests │ ├── Info.plist │ └── CocoaHeadsNLUITests.swift └── .swiftlint.yml ├── .swiftlint.yml ├── logo1024.png ├── Photoshop ├── AppIcon.psd ├── Banner.psd ├── JobsTabIcon.psd ├── EventsTabIcon.psd ├── CompaniesTabIcon.psd └── MeetupPlaceholder.psd ├── provisioning ├── AppStoreCertificates.p12.gpg ├── CocoaHeadsNL-AppStore.mobileprovision.gpg ├── CocoaHeadsNL-AppStore-Item-Notification.mobileprovision.gpg ├── CocoaHeadsNL-AppStore-General-Notification.mobileprovision.gpg ├── CocoaHeadsNL-AppStore-Item-Service-Extension.mobileprovision.gpg ├── README.md ├── App-Store.plist └── import_provisioning.sh ├── .travis.yml ├── .gitignore ├── azure-pipelines.yml ├── CONTRIBUTING.md ├── .github └── workflows │ ├── ci.yml │ └── deploy.yml └── README.md /CocoaHeadsNL/.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj merge=union 2 | *.strings text diff 3 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - line_length 3 | excluded: 4 | - Pods 5 | -------------------------------------------------------------------------------- /logo1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/logo1024.png -------------------------------------------------------------------------------- /Photoshop/AppIcon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/Photoshop/AppIcon.psd -------------------------------------------------------------------------------- /Photoshop/Banner.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/Photoshop/Banner.psd -------------------------------------------------------------------------------- /Photoshop/JobsTabIcon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/Photoshop/JobsTabIcon.psd -------------------------------------------------------------------------------- /CocoaHeadsNL/.xctool-args: -------------------------------------------------------------------------------- 1 | [ 2 | "-workspace", "CocoaHeadsNL.xcworkspace", 3 | "-scheme", "CocoaHeadsNL" 4 | ] 5 | 6 | -------------------------------------------------------------------------------- /Photoshop/EventsTabIcon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/Photoshop/EventsTabIcon.psd -------------------------------------------------------------------------------- /Photoshop/CompaniesTabIcon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/Photoshop/CompaniesTabIcon.psd -------------------------------------------------------------------------------- /Photoshop/MeetupPlaceholder.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/Photoshop/MeetupPlaceholder.psd -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /provisioning/AppStoreCertificates.p12.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/provisioning/AppStoreCertificates.p12.gpg -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/CocoaHeadsNL500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/CocoaHeadsNL500.png -------------------------------------------------------------------------------- /provisioning/CocoaHeadsNL-AppStore.mobileprovision.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/provisioning/CocoaHeadsNL-AppStore.mobileprovision.gpg -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/CocoaHeadsNL-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/Banner@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/Banner@1x.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/Banner@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/Banner@2x.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/Banner@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/Banner@3x.png -------------------------------------------------------------------------------- /provisioning/CocoaHeadsNL-AppStore-Item-Notification.mobileprovision.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/provisioning/CocoaHeadsNL-AppStore-Item-Notification.mobileprovision.gpg -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AboutTabIcon.imageset/about-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AboutTabIcon.imageset/about-1.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AboutTabIcon.imageset/about-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AboutTabIcon.imageset/about-2.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AboutTabIcon.imageset/about-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AboutTabIcon.imageset/about-3.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-120.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-152.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-167.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-176.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-176.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-180.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-196.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-29.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-40.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-48.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-55.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-58.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-76.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-80.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-87.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/BannerDark@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/BannerDark@1x.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/BannerDark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/BannerDark@2x.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/BannerDark@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/BannerDark@3x.png -------------------------------------------------------------------------------- /provisioning/CocoaHeadsNL-AppStore-General-Notification.mobileprovision.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/provisioning/CocoaHeadsNL-AppStore-General-Notification.mobileprovision.gpg -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-120-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-120-1.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-58-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-58-1.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-58-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-58-2.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-80-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-80-1.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-80-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-80-2.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-87-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/AppIcon-87-1.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/JobsTabIcon.imageset/JobsTabIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/JobsTabIcon.imageset/JobsTabIcon.png -------------------------------------------------------------------------------- /provisioning/CocoaHeadsNL-AppStore-Item-Service-Extension.mobileprovision.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/provisioning/CocoaHeadsNL-AppStore-Item-Service-Extension.mobileprovision.gpg -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/JobsTabIcon.imageset/JobsTabIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/JobsTabIcon.imageset/JobsTabIcon@2x.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/JobsTabIcon.imageset/JobsTabIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/JobsTabIcon.imageset/JobsTabIcon@3x.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/LogoCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import CloudKit 3 | 4 | class LogoCell: UITableViewCell { 5 | @IBOutlet weak var logoImageView: UIImageView! 6 | } 7 | 8 | extension LogoCell: Identifiable {} 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | xcode_project: CocoaHeadsNL/CocoaHeadsNL.xcodeproj 3 | xcode_scheme: CocoaHeadsNL 4 | xcode_destination: platform=iOS Simulator,OS=13.3,name=iPhone 11 5 | sudo: false 6 | osx_image: xcode11.3 7 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CocoaHeadsNLLogo.imageset/CocoaHeadsNL.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CocoaHeadsNLLogo.imageset/CocoaHeadsNL.pdf -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/EventsTabIcon.imageset/EventsTabIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/EventsTabIcon.imageset/EventsTabIcon.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CocoaHeadsNLLogo.imageset/CocoaHeadsNL-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CocoaHeadsNLLogo.imageset/CocoaHeadsNL-1.pdf -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/EventsTabIcon.imageset/EventsTabIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/EventsTabIcon.imageset/EventsTabIcon@2x.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/EventsTabIcon.imageset/EventsTabIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/EventsTabIcon.imageset/EventsTabIcon@3x.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CompaniesTabIcon.imageset/CompaniesTabIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CompaniesTabIcon.imageset/CompaniesTabIcon.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/MeetupPlaceholder.imageset/MeetupPlaceholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/MeetupPlaceholder.imageset/MeetupPlaceholder.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CocoaHeadsNLLogoOnlyLight.imageset/CocoaHeadsNL.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CocoaHeadsNLLogoOnlyLight.imageset/CocoaHeadsNL.pdf -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/MeetupPlaceholder.imageset/MeetupPlaceholder@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/MeetupPlaceholder.imageset/MeetupPlaceholder@2x.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/MeetupPlaceholder.imageset/MeetupPlaceholder@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/MeetupPlaceholder.imageset/MeetupPlaceholder@3x.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CompaniesTabIcon.imageset/CompaniesTabIconStroked@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CompaniesTabIcon.imageset/CompaniesTabIconStroked@2x.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CompaniesTabIcon.imageset/CompaniesTabIconStroked@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CocoaHeadsNL/CocoaHeadsNL-iOS/HEAD/CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CompaniesTabIcon.imageset/CompaniesTabIconStroked@3x.png -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/MainViewContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabBarContainer.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Bart Hoffman on 16/03/15. 6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | -------------------------------------------------------------------------------- /CocoaHeadsNL/itemNotification/itemNotification.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /CocoaHeadsNL/generalNotification/generalNotification.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/UIColor+Extensions.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public func UIColorWithRGB(_ red: Int, green: Int, blue: Int) -> UIColor { 4 | return UIColor(red: CGFloat(red) / 255.0, 5 | green: CGFloat(green) / 255.0, 6 | blue: CGFloat(blue) / 255.0, 7 | alpha: 1.0) 8 | } 9 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Job+CoreDataClass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Job+CoreDataClass.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Jeroen Leenarts on 20-02-18. 6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | // 9 | 10 | import Foundation 11 | import CoreData 12 | 13 | @objc(Job) 14 | public class Job: NSManagedObject { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CocoaHeadsNLLogoOnlyLight.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "CocoaHeadsNL.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/TabBarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabBarController.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Bart Hoffman on 15/03/15. 6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class TabBarController: UITabBarController, UITabBarControllerDelegate { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Meetup+CoreDataClass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Meetup+CoreDataClass.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Jeroen Leenarts on 20-02-18. 6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | // 9 | 10 | import Foundation 11 | import CoreData 12 | 13 | @objc(Meetup) 14 | public class Meetup: NSManagedObject { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Company+CoreDataClass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Company+CoreDataClass.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Jeroen Leenarts on 20-02-18. 6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | // 9 | 10 | import Foundation 11 | import CoreData 12 | 13 | @objc(Company) 14 | public class Company: NSManagedObject { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/ParseConfig-template.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | applicationId 6 | PARSE_APPLICATION_ID 7 | clientKey 8 | PARSE_CLIENT_KEY 9 | 10 | 11 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Contributor+CoreDataClass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Contributor+CoreDataClass.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Jeroen Leenarts on 20-02-18. 6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | // 9 | 10 | import Foundation 11 | import CoreData 12 | 13 | @objc(Contributor) 14 | public class Contributor: NSManagedObject { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /CocoaHeadsNL/ItemServiceExtension/ItemServiceExtension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.security.app-sandbox 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /CocoaHeadsNL/.gitignore: -------------------------------------------------------------------------------- 1 | # OS X Finder 2 | .DS_Store 3 | 4 | # Xcode per-user config 5 | *.mode1 6 | *.mode1v3 7 | *.mode2v3 8 | *.perspective 9 | *.perspectivev3 10 | *.pbxuser 11 | xcuserdata 12 | *.xccheckout 13 | 14 | # Build products 15 | build/ 16 | *.o 17 | *.LinkFileList 18 | *.hmap 19 | 20 | # Automatic backup files 21 | *~.nib/ 22 | *.swp 23 | *~ 24 | *.dat 25 | *.dep 26 | 27 | # Cocoapods 28 | Pods 29 | 30 | # AppCode specific files 31 | .idea/ 32 | *.iml 33 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/NSLocalizedString+Convenience.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSLocalizedString+Convenience.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Bruno Scheele on 18/01/2017. 6 | // Copyright © 2017 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Returns `NSLocalizedString(key, comment: "")` 12 | public func NSLocalizedString(_ key: String) -> String { 13 | return NSLocalizedString(key, comment: "") 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CocoaHeadsNL/CloudCode/config/global.json 2 | CocoaHeadsNL/CocoaHeadsNL/Settings.bundle/Acknowledgements.plist 3 | CocoaHeadsNL/CocoaHeadsNL/ParseConfig.plist 4 | CocoaHeadsNL/Pods 5 | xcuserdata 6 | CocoaHeadsNL/parse 7 | 8 | screenshots 9 | report.xml 10 | Error*.png 11 | *.app.dSym.zip 12 | *.ipa 13 | *.itmsp 14 | *.mobileprovision 15 | config.js 16 | eckey.pem 17 | CocoaHeadsNL/CloudKitSync/node_modules 18 | cloudkit.js 19 | config.js.dev 20 | config.js.prod 21 | 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CocoaHeadsNLLogo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "CocoaHeadsNL.pdf", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "CocoaHeadsNL-1.pdf", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AboutTabIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "about-1.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "about-2.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "about-3.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/JobsTabIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "JobsTabIcon.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "JobsTabIcon@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "JobsTabIcon@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # Build, test, and archive an Xcode workspace on macOS. 3 | # Add steps that install certificates, test, sign, and distribute an app, save build artifacts, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/xcode 5 | 6 | pool: 7 | vmImage: 'macos-latest' 8 | 9 | trigger: 10 | - master 11 | - feature/* 12 | 13 | steps: 14 | - task: Xcode@5 15 | inputs: 16 | actions: 'build' 17 | scheme: 'CocoaHeadsNL' 18 | sdk: 'iphoneos' 19 | configuration: 'Debug' 20 | xcodeVersion: '11' 21 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/EventsTabIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "EventsTabIcon.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "EventsTabIcon@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "EventsTabIcon@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/MeetupPlaceholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "MeetupPlaceholder.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "MeetupPlaceholder@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "MeetupPlaceholder@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /CocoaHeadsNL/CloudKitSync/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CocoaHeadsNLAppSync", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "generate-config": "cp config.js.example config.js", 9 | "install-cloudkit-js": "curl https://cdn.apple-cloudkit.com/ck/1/cloudkit.js > cloudkit.js" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "node-fetch": "^1.3.3", 15 | "request": "^2.67.0", 16 | "promise": "^7.0.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/CompaniesTabIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "CompaniesTabIcon.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "CompaniesTabIconStroked@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "CompaniesTabIconStroked@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/MapAnnotation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapAnnotation.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Bart Hoffman on 14/03/15. 6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import MapKit 10 | 11 | class MapAnnotation: NSObject, MKAnnotation { 12 | var coordinate: CLLocationCoordinate2D 13 | var title: String? 14 | var subtitle: String? 15 | 16 | init(coordinate: CLLocationCoordinate2D, title: String, subtitle: String) { 17 | self.coordinate = coordinate 18 | self.title = title 19 | self.subtitle = subtitle 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We welcome any additions to this repository. Please note, all contributions are considered donations to Stichting CocoaHeadsNL. Contributing constitutes transfer of copyright. 4 | 5 | ## Make sure to check with us before starting your work. 6 | 7 | Join us on Slack: [https://join.slack.com/t/cocoaheadsnl/shared_invite/enQtNjQwMjQzNzgxODQ2LThkYWY3ZDhkOGMyMmQ1ZTZkMzc3ZjYxY2VjMzY4MDkwOTEzYTJhZWNjYTEzMzBiMGM5ZjY4MDAzMDNiNDE2NzA](https://join.slack.com/t/cocoaheadsnl/shared_invite/enQtNjQwMjQzNzgxODQ2LThkYWY3ZDhkOGMyMmQ1ZTZkMzc3ZjYxY2VjMzY4MDkwOTEzYTJhZWNjYTEzMzBiMGM5ZjY4MDAzMDNiNDE2NzA) 8 | 9 | 10 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/UINavigationItem+Banner.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension UINavigationItem { 4 | 5 | func setupForRootViewController(withTitle title: String) { 6 | 7 | let imageView = UIImageView(image: UIImage(named: "Banner")) 8 | imageView.accessibilityTraits = UIAccessibilityTraits(rawValue: imageView.accessibilityTraits.rawValue & ~UIAccessibilityTraits.image.rawValue) 9 | imageView.accessibilityTraits = UIAccessibilityTraits(rawValue: imageView.accessibilityTraits.rawValue | UIAccessibilityTraits.header.rawValue) 10 | self.titleView = imageView 11 | 12 | self.title = title 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/UIImage+Resize.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UIImage { 4 | func resizedImageWithBounds(_ bounds: CGSize) -> UIImage { 5 | let horizontalRatio = bounds.width / size.width 6 | let verticalRatio = bounds.height / size.height 7 | let ratio = min(horizontalRatio, verticalRatio) 8 | let newSize = CGSize(width: ceil(size.width * ratio), height: ceil(size.height * ratio)) 9 | 10 | UIGraphicsBeginImageContextWithOptions(newSize, false, 0) 11 | draw(in: CGRect(origin: CGPoint.zero, size: newSize)) 12 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 13 | UIGraphicsEndImageContext() 14 | 15 | return newImage! 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /provisioning/README.md: -------------------------------------------------------------------------------- 1 | We create the gpg files by running commands like... 2 | ``` 3 | gpg --symmetric --cipher-algo AES256 CocoaHeadsNL-AppStore.mobileprovision 4 | gpg --symmetric --cipher-algo AES256 CocoaHeadsNL-AppStore-Item-Service-Extension.mobileprovision 5 | gpg --symmetric --cipher-algo AES256 CocoaHeadsNL-AppStore-Item-Notification.mobileprovision 6 | gpg --symmetric --cipher-algo AES256 CocoaHeadsNL-AppStore-General-Notification.mobileprovision 7 | gpg --symmetric --cipher-algo AES256 AppStoreCertificates.p12 8 | ``` 9 | 10 | The password on the P12 file is the same as the password used to perform the encryption. The password is also stored on Github as a secret in the env var `PROVISIONING_PASSWORD`. 11 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/OnlineCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnlineCell.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Jeroen Leenarts on 19/04/2020. 6 | // Copyright © 2020 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class OnlineCell: UITableViewCell { 12 | 13 | @IBOutlet weak var onlineButton: UIButton! 14 | @IBAction func buttonPressed(_ sender: AnyObject) { 15 | if let url = URL(string: "https://youtube.com/stichtingcocoaheadsnl"), UIApplication.shared.canOpenURL(url) { 16 | UIApplication.shared.open(url, options: [:], completionHandler: { _ in 17 | }) 18 | } 19 | } 20 | 21 | } 22 | 23 | extension OnlineCell: Identifiable {} 24 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Identifiable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Identifiable.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Jeroen Leenarts on 10/01/2020. 6 | // Copyright © 2020 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol Identifiable: class { 12 | static var identifier: String { get } 13 | } 14 | 15 | public extension Identifiable { 16 | 17 | static var identifier: String { 18 | return String(describing: Self.self) 19 | } 20 | } 21 | 22 | extension UIStoryboard { 23 | 24 | public func instantiateViewController(type: T.Type) -> T where T: UIViewController { 25 | return instantiateViewController(withIdentifier: type.identifier) as! T // swiftlint:disable:this force_cast 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/DayLabelColorHistory.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "gray-gamma-22", 11 | "components" : { 12 | "white" : "0.000", 13 | "alpha" : "0.650" 14 | } 15 | } 16 | }, 17 | { 18 | "idiom" : "universal", 19 | "appearances" : [ 20 | { 21 | "appearance" : "luminosity", 22 | "value" : "dark" 23 | } 24 | ], 25 | "color" : { 26 | "color-space" : "gray-gamma-22", 27 | "components" : { 28 | "white" : "1.000", 29 | "alpha" : "0.650" 30 | } 31 | } 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNLUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/NSAttributedStringExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedStringExtension.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Sidney de Koning on 29/05/2017. 6 | // Copyright © 2017 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension NSAttributedString { 12 | 13 | internal convenience init?(html: String) { 14 | guard let data = html.data(using: String.Encoding.utf16, allowLossyConversion: false) else { 15 | return nil 16 | } 17 | 18 | guard let attributedString = try? NSMutableAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) else { 19 | return nil 20 | } 21 | 22 | self.init(attributedString: attributedString) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Contributor+CoreDataProperties.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Contributor+CoreDataProperties.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Jeroen Leenarts on 20-02-18. 6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | // 9 | 10 | import Foundation 11 | import CoreData 12 | 13 | extension Contributor { 14 | 15 | @nonobjc public class func fetchRequest() -> NSFetchRequest { 16 | return NSFetchRequest(entityName: "Contributor") 17 | } 18 | 19 | @NSManaged public var recordID: NSObject? 20 | @NSManaged public var recordName: String? 21 | @NSManaged public var avatarUrl: String? 22 | @NSManaged public var contributorId: Int64 23 | @NSManaged public var commitCount: Int32 24 | @NSManaged public var name: String? 25 | @NSManaged public var url: String? 26 | 27 | } 28 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Job+CoreDataProperties.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Job+CoreDataProperties.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Jeroen Leenarts on 20-02-18. 6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | // 9 | 10 | import Foundation 11 | import CoreData 12 | 13 | extension Job { 14 | 15 | @nonobjc public class func fetchRequest() -> NSFetchRequest { 16 | return NSFetchRequest(entityName: "Job") 17 | } 18 | 19 | @NSManaged public var recordID: NSObject? 20 | @NSManaged public var recordName: String? 21 | @NSManaged public var content: String? 22 | @NSManaged public var date: Date? 23 | @NSManaged public var link: String? 24 | @NSManaged public var title: String? 25 | @NSManaged public var logoUrlString: String? 26 | @NSManaged public var logo: NSData? 27 | @NSManaged public var companyName: String? 28 | 29 | } 30 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/DayLabelColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.000", 13 | "alpha" : "1.000", 14 | "blue" : "0.000", 15 | "green" : "0.000" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "1.000", 31 | "alpha" : "1.000", 32 | "blue" : "1.000", 33 | "green" : "1.000" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/DayBackgroundViewColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "232", 13 | "alpha" : "1.000", 14 | "blue" : "80", 15 | "green" : "88" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0.117", 31 | "alpha" : "1.000", 32 | "blue" : "0.653", 33 | "green" : "0.639" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/DayBackgroundViewColorHistory.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "169", 13 | "alpha" : "1.000", 14 | "blue" : "166", 15 | "green" : "166" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0.500", 31 | "alpha" : "1.000", 32 | "blue" : "0.808", 33 | "green" : "0.200" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/DayBackgroundViewColorToday.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "127", 13 | "alpha" : "1.000", 14 | "blue" : "33", 15 | "green" : "214" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0.890", 31 | "alpha" : "1.000", 32 | "blue" : "0.808", 33 | "green" : "0.200" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/CoreDataStack.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CoreData 3 | 4 | final class CoreDataStack { 5 | static let shared = CoreDataStack() 6 | 7 | lazy var persistentContainer: NSPersistentContainer = { 8 | let container = NSPersistentContainer(name: "Model") 9 | container.loadPersistentStores { _, error in 10 | if let error = error { 11 | print("Unable to Load Persistent Store") 12 | print("\(error), \(error.localizedDescription)") 13 | } 14 | } 15 | container.viewContext.automaticallyMergesChangesFromParent = true 16 | 17 | return container 18 | }() 19 | 20 | var viewContext: NSManagedObjectContext { 21 | return persistentContainer.viewContext 22 | } 23 | 24 | var newBackgroundContext: NSManagedObjectContext { 25 | return persistentContainer.newBackgroundContext() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | # Trigger the workflow on push or pull request, 5 | # but only for the master branch 6 | push: 7 | branches: 8 | - master 9 | pull_request: 10 | branches: 11 | - master 12 | jobs: 13 | SwiftLint: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v1 17 | - name: GitHub Action for SwiftLint 18 | uses: norio-nomura/action-swiftlint@3.0.1 19 | 20 | Test: 21 | runs-on: macOS-latest 22 | steps: 23 | - uses: actions/checkout@v1 24 | - name: List available Xcode versions 25 | run: ls /Applications | grep Xcode 26 | - name: Select Xcode 27 | run: sudo xcode-select -switch /Applications/Xcode_12.app && /usr/bin/xcodebuild -version 28 | - name: Run unit tests 29 | run: xcodebuild test -scheme CocoaHeadsNL -project CocoaHeadsNL/CocoaHeadsNL.xcodeproj -destination 'platform=iOS Simulator,name=iPhone 11,OS=14.0' | xcpretty && exit ${PIPESTATUS[0]} 30 | 31 | 32 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/JobDataSource.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class JobDataSource: DetailDataSource { 4 | var job: Job { 5 | return object as! Job // swiftlint:disable:this force_cast 6 | } 7 | 8 | override var title: String? { 9 | return job.title 10 | } 11 | 12 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 13 | return 3 14 | } 15 | 16 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 17 | switch (indexPath as NSIndexPath).row { 18 | case 0: 19 | return logoCellWithFile(job.logoImage, forTableView: tableView, forRowAt: indexPath) 20 | case 1: 21 | return titleCellWithText(job.title, forTableView: tableView, forRowAt: indexPath) 22 | case 2: 23 | return dataCellWithHTML(job.content, forTableView: tableView, forRowAt: indexPath) 24 | default: 25 | fatalError("This should not happen.") 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/CocoaHeadsNL.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.associated-domains 8 | 9 | applinks:cocoaheads.nl 10 | webcredentials:*.cocoaheads.nl 11 | 12 | com.apple.developer.icloud-container-identifiers 13 | 14 | iCloud.nl.cocoaheads.app.CocoaHeadsNL 15 | 16 | com.apple.developer.icloud-services 17 | 18 | CloudKit 19 | 20 | com.apple.developer.ubiquity-kvstore-identifier 21 | $(TeamIdentifierPrefix)$(CFBundleIdentifier) 22 | com.apple.security.app-sandbox 23 | 24 | com.apple.security.network.client 25 | 26 | com.apple.security.personal-information.location 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CloudKitSync/config.js.example: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2016 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | This is the container configuration that gets passed to CloudKit.configure. 7 | Customize this for your own environment. 8 | */ 9 | 10 | module.exports.githubAuthOptions = {'user': '', 'pass': ''} 11 | 12 | module.exports.meetupApiKey = "" 13 | 14 | module.exports.slackHookUrl = undefined; // 15 | 16 | module.exports.containerConfig = { 17 | // Replace this with a container that you own. 18 | containerIdentifier:'iCloud.nl.cocoaheads.app.CocoaHeadsNL', 19 | 20 | environment: 'development', 21 | 22 | serverToServerKeyAuth: { 23 | // Generate a key ID through CloudKit Dashboard and insert it here. 24 | keyID: '', 25 | 26 | // This should reference the private key file that you used to generate the above key ID. 27 | privateKeyFile: __dirname + '/eckey.pem' 28 | } 29 | }; -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Company+CoreDataProperties.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Company+CoreDataProperties.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Jeroen Leenarts on 20-02-18. 6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | // 9 | 10 | import Foundation 11 | import CoreData 12 | 13 | extension Company { 14 | 15 | @nonobjc public class func fetchRequest() -> NSFetchRequest { 16 | return NSFetchRequest(entityName: "Company") 17 | } 18 | 19 | @NSManaged public var recordID: NSObject? 20 | @NSManaged public var recordName: String? 21 | @NSManaged public var name: String? 22 | @NSManaged public var place: String? 23 | @NSManaged public var website: String? 24 | @NSManaged public var streetAddress: String? 25 | @NSManaged public var zipCode: String? 26 | @NSManaged public var companyDescription: String? 27 | @NSManaged public var emailAddress: String? 28 | @NSManaged public var latitude: Double 29 | @NSManaged public var longitude: Double 30 | @NSManaged public var logo: NSData? 31 | @NSManaged public var smallLogo: NSData? 32 | 33 | } 34 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/HTMLDataCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTMLDataCell.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Sidney de Koning on 29/05/2017. 6 | // Copyright © 2017 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HTMLDataCell: UITableViewCell { 12 | 13 | @IBOutlet weak var textView: UITextView! 14 | 15 | var html: String? { 16 | didSet { 17 | guard let html = html, html != oldValue else { return } 18 | let inputText = "\(html)" 19 | let attributedString = NSAttributedString(html: inputText) 20 | 21 | self.textView.attributedText = attributedString 22 | self.textView.isScrollEnabled = false 23 | self.textView.delegate = self 24 | } 25 | } 26 | } 27 | 28 | extension HTMLDataCell: UITextViewDelegate { 29 | 30 | func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { 31 | UIApplication.shared.open(URL, options: [:]) 32 | return false 33 | } 34 | } 35 | 36 | extension HTMLDataCell: Identifiable {} 37 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/UIAlertController+FetchErrors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIAlertController+FetchErrors.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Bruno Scheele on 18/01/2017. 6 | // Copyright © 2017 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIAlertController { 12 | /// Returns a UIAlertController with the .alert style for a failed fetch request. 13 | /// 14 | /// - Parameter fetching: What failed to fetch. Will be prefixed with 'the list of ' (e.g. 'the list of ') 15 | /// - Returns: A UIAlertController to display. 16 | static func fetchErrorDialog(whileFetching fetching: String, error: Error) -> UIAlertController { 17 | let title = NSLocalizedString("Fetch failed") 18 | let message = String(format: NSLocalizedString("There was a problem fetching the list of %@; please try again.\n%@"), fetching, error.localizedDescription) 19 | let okButtonTitle = NSLocalizedString("OK") 20 | 21 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) 22 | alertController.addAction(UIAlertAction(title: okButtonTitle, style: .default, handler: nil)) 23 | return alertController 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CloudKitSync/jobs/loadEventInfo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Promise = require('promise'); 4 | var request = require('request'); 5 | var config = require('../config'); 6 | 7 | exports.load = function() { 8 | var loadEventInfoPromise = new Promise(function(resolve, reject) { 9 | 10 | var options = { 11 | url: 'https://api.meetup.com/2/events?&sign=true&photo-host=public&group_urlname=cocoaheadsnl&desc=true&status=upcoming,past&key='+config.meetupApiKey, 12 | headers: { 'User-Agent': 'CocoaHeadsNL-Cloud-Sync', 'Accept-Charset': 'UTF-8'}, 13 | } 14 | 15 | request(options, function(error, response, body){ 16 | if (error) { 17 | reject(error); 18 | console.log("Error on fetching meetup information: " + error) 19 | return; 20 | } 21 | 22 | if (response.statusCode != 200) { 23 | reject(response.statusCode); 24 | console.log("Invalid HTTP status code on meetup information: " + response.statusCode) 25 | return; 26 | } 27 | resolve(body); 28 | }); 29 | }).then(function(body) { 30 | var meetupData = JSON.parse(body); 31 | return Promise.resolve(meetupData.results) 32 | }) 33 | 34 | return loadEventInfoPromise 35 | } 36 | -------------------------------------------------------------------------------- /provisioning/App-Store.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | compileBitcode 6 | 7 | iCloudContainerEnvironment 8 | Production 9 | method 10 | app-store 11 | teamID 12 | D55PL7PU7S 13 | uploadBitcode 14 | 15 | uploadSymbols 16 | 17 | signingStyle 18 | manual 19 | signingCertificate 20 | Apple Distribution: Stichting CocoaheadsNL 21 | provisioningProfiles 22 | 23 | nl.cocoaheads.app.CocoaHeadsNL.generalNotification 24 | f310cafd-4976-454e-9a54-bd5e5f6ce2c9 25 | nl.cocoaheads.app.CocoaHeadsNL.itemNotification 26 | c5853b6d-9a45-41a4-90bd-e12344b6b613 27 | nl.cocoaheads.app.CocoaHeadsNL.ItemServiceExtension 28 | b2a2ec45-f1f8-47b3-9200-09c808d989b2 29 | nl.cocoaheads.app.CocoaHeadsNL 30 | 979eaa25-8ce3-4e21-a59e-5ec136f57adb 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/RequestReview.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestReview.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Jeroen Leenarts on 29/03/2017. 6 | // Copyright © 2017 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import StoreKit 10 | 11 | public class RequestReview: NSObject { 12 | 13 | private static let defaultKeyRatingLastRequestDate = "app_rating_last_request_date" 14 | 15 | public static func requestReview() { 16 | if let lastRequestDate = UserDefaults.standard.object(forKey: RequestReview.defaultKeyRatingLastRequestDate) as? Date { 17 | if lastRequestDate.compare(Date()) == ComparisonResult.orderedAscending { 18 | SKStoreReviewController.requestReview() 19 | let reviewRequestDate = NSCalendar.current.date(byAdding: .day, value: 100, to: Date()) 20 | UserDefaults.standard.set(reviewRequestDate, forKey: defaultKeyRatingLastRequestDate) 21 | } 22 | } else { 23 | // Ask for first review 7 days from now 24 | let reviewRequestDate = NSCalendar.current.date(byAdding: .day, value: 7, to: Date()) 25 | UserDefaults.standard.set(reviewRequestDate, forKey: defaultKeyRatingLastRequestDate) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/Banner.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Banner@1x.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "BannerDark@1x.png", 11 | "appearances" : [ 12 | { 13 | "appearance" : "luminosity", 14 | "value" : "dark" 15 | } 16 | ], 17 | "scale" : "1x" 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "filename" : "Banner@2x.png", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "idiom" : "universal", 26 | "filename" : "BannerDark@2x.png", 27 | "appearances" : [ 28 | { 29 | "appearance" : "luminosity", 30 | "value" : "dark" 31 | } 32 | ], 33 | "scale" : "2x" 34 | }, 35 | { 36 | "idiom" : "universal", 37 | "filename" : "Banner@3x.png", 38 | "scale" : "3x" 39 | }, 40 | { 41 | "idiom" : "universal", 42 | "filename" : "BannerDark@3x.png", 43 | "appearances" : [ 44 | { 45 | "appearance" : "luminosity", 46 | "value" : "dark" 47 | } 48 | ], 49 | "scale" : "3x" 50 | } 51 | ], 52 | "info" : { 53 | "version" : 1, 54 | "author" : "xcode" 55 | } 56 | } -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/TitleCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class TitleCell: UITableViewCell { 4 | @IBOutlet weak var titleLabel: UILabel! 5 | 6 | var content: String? { 7 | didSet { 8 | self.titleLabel.text = content 9 | } 10 | } 11 | 12 | lazy var dateFormatter: DateFormatter = { 13 | let dateFormatter = DateFormatter() 14 | let dateFormat = DateFormatter.dateFormat(fromTemplate: "MMMMd", 15 | options: 0, 16 | locale: Locale.current) 17 | dateFormatter.dateFormat = dateFormat 18 | return dateFormatter 19 | }() 20 | 21 | lazy var timeFormatter: DateFormatter = { 22 | let dateFormatter = DateFormatter() 23 | dateFormatter.timeStyle = .short 24 | return dateFormatter 25 | }() 26 | 27 | var date: Date? { 28 | didSet { 29 | if let date = date { 30 | let dateString = dateFormatter.string(from: date) 31 | let timeString = timeFormatter.string(from: date) 32 | self.titleLabel.text = dateString + ", " + timeString 33 | } else { 34 | self.titleLabel.text = "-" 35 | } 36 | } 37 | } 38 | } 39 | 40 | extension TitleCell: Identifiable {} 41 | -------------------------------------------------------------------------------- /CocoaHeadsNL/generalNotification/NotificationViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationViewController.swift 3 | // generalNotification 4 | // 5 | // Created by Bart Hoffman on 10/03/2018. 6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import UserNotifications 11 | import UserNotificationsUI 12 | 13 | class NotificationViewController: UIViewController, UNNotificationContentExtension { 14 | 15 | @IBOutlet var postImageView: UIImageView? 16 | @IBOutlet var titleLabel: UILabel? 17 | @IBOutlet var bodyLabel: UILabel? 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | // Do any required interface initialization here. 22 | } 23 | 24 | func didReceive(_ notification: UNNotification) { 25 | 26 | self.titleLabel?.text = notification.request.content.title 27 | self.bodyLabel?.text = notification.request.content.body 28 | 29 | if let attachment = notification.request.content.attachments.first { 30 | if attachment.url.startAccessingSecurityScopedResource() { 31 | if let data = NSData(contentsOfFile: attachment.url.path) as Data? { 32 | self.postImageView?.image = UIImage(data: data) 33 | attachment.url.stopAccessingSecurityScopedResource() 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /CocoaHeadsNL/itemNotification/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | itemNotification 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | $(CURRENT_PROJECT_VERSION) 21 | CFBundleVersion 22 | auto-generated 23 | NSExtension 24 | 25 | NSExtensionAttributes 26 | 27 | UNNotificationExtensionCategory 28 | nl.cocoaheads.app.CocoaHeadsNL.itemNotification 29 | UNNotificationExtensionInitialContentSizeRatio 30 | 1 31 | 32 | NSExtensionMainStoryboard 33 | MainInterface 34 | NSExtensionPointIdentifier 35 | com.apple.usernotifications.content-extension 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /CocoaHeadsNL/ItemServiceExtension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | ItemServiceExtension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | $(CURRENT_PROJECT_VERSION) 21 | CFBundleVersion 22 | auto-generated 23 | NSAppTransportSecurity 24 | 25 | NSExceptionDomains 26 | 27 | cocoaheads.nl 28 | 29 | NSExceptionRequiresForwardSecrecy 30 | 31 | 32 | 33 | 34 | NSExtension 35 | 36 | NSExtensionPointIdentifier 37 | com.apple.usernotifications.service 38 | NSExtensionPrincipalClass 39 | $(PRODUCT_MODULE_NAME).NotificationService 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | # Trigger the workflow on push or pull request, 5 | # but only for the master branch 6 | push: 7 | branches: 8 | - master 9 | jobs: 10 | Build: 11 | runs-on: macOS-latest 12 | steps: 13 | - uses: actions/checkout@v1 14 | - name: Install GPG 15 | run: brew install gnupg 16 | - name: List available Xcode versions 17 | run: ls /Applications | grep Xcode 18 | - name: Decrypt large secret 19 | run: ./provisioning/import_provisioning.sh 20 | env: 21 | PROVISIONING_PASSWORD: ${{ secrets.provisioning_password }} 22 | - name: Select Xcode 23 | run: sudo xcode-select -switch /Applications/Xcode_12.app && /usr/bin/xcodebuild -version 24 | - name: Build archive 25 | run: xcodebuild -sdk iphoneos -project CocoaHeadsNL/CocoaHeadsNL.xcodeproj -configuration Release -scheme CocoaHeadsNL -derivedDataPath DerivedData -archivePath DerivedData/Archive/CocoaHeadsNL archive 26 | - name: Export Archive 27 | run: xcodebuild -exportArchive -archivePath DerivedData/Archive/CocoaHeadsNL.xcarchive -exportOptionsPlist provisioning/App-Store.plist -exportPath DerivedData/ipa 28 | - name: Dump file hierarchy 29 | run: ls -R 30 | - name: Deploy App to Apple 31 | run: xcrun altool --upload-app --type ios --file DerivedData/ipa/CocoaHeadsNL.ipa --username "${{ secrets.appstore_connect_username }}" --password "${{ secrets.appstore_connect_password }}" --verbose 32 | -------------------------------------------------------------------------------- /CocoaHeadsNL/generalNotification/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | generalNotification 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | $(CURRENT_PROJECT_VERSION) 21 | CFBundleVersion 22 | auto-generated 23 | NSExtension 24 | 25 | NSExtensionAttributes 26 | 27 | UNNotificationExtensionCategory 28 | nl.cocoaheads.app.CocoaHeadsNL.generalNotification 29 | UNNotificationExtensionInitialContentSizeRatio 30 | 1 31 | UNNotificationExtensionDefaultContentHidden 32 | 33 | 34 | NSExtensionMainStoryboard 35 | MainInterface 36 | NSExtensionPointIdentifier 37 | com.apple.usernotifications.content-extension 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Meetup+CoreDataProperties.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Meetup+CoreDataProperties.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Jeroen Leenarts on 20-02-18. 6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | // 9 | 10 | import Foundation 11 | import CoreData 12 | 13 | extension Meetup { 14 | 15 | @nonobjc public class func fetchRequest() -> NSFetchRequest { 16 | return NSFetchRequest(entityName: "Meetup") 17 | } 18 | 19 | @NSManaged public var recordID: NSObject? 20 | @NSManaged public var recordName: String? 21 | @NSManaged public var duration: Int32 22 | @NSManaged public var latitude: Double 23 | @NSManaged public var longitude: Double 24 | @NSManaged public var locationName: String? 25 | @NSManaged public var meetupDescription: String? 26 | @NSManaged public var meetupId: String? 27 | @NSManaged public var name: String? 28 | @NSManaged public var rsvpLimit: Int32 29 | @NSManaged public var time: Date? 30 | @NSManaged public var yesRsvpCount: Int32 31 | @NSManaged public var logo: NSData? 32 | @NSManaged public var nextEvent: Bool 33 | @NSManaged public var smallLogo: NSData? 34 | @NSManaged public var location: String? 35 | @NSManaged public var meetupUrl: String? 36 | @NSManaged public var year: Int32 37 | 38 | @objc 39 | var sectionName: String { 40 | if isToday { 41 | return NSLocalizedString("Today", comment: "Section title for todays meetup.") 42 | } 43 | if isUpcoming { 44 | return NSLocalizedString("Upcoming", comment: "Section title for upcoming meetups.") 45 | } 46 | 47 | return "\(year)" 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CloudKitSync/README.md: -------------------------------------------------------------------------------- 1 | Code and instructions derived from [Apple Sample code](https://developer.apple.com/library/prerelease/ios/samplecode/CloudAtlas/Introduction/Intro.html#//apple_ref/doc/uid/TP40014599). 2 | 3 | ## Install dependencies 4 | 5 | Our script uses the npm module *node-fetch* and the *CloudKit JS* library. Install them by running the following 6 | commands from the same directory as this README. 7 | ``` 8 | npm install 9 | npm run-script generate-config 10 | npm run-script install-cloudkit-js 11 | ``` 12 | 13 | ## Generate a private key 14 | 15 | If you are using a Mac, you already have OpenSSL installed and you can generate a private key with this command (make 16 | sure you are in the same directory as this README). 17 | ``` 18 | openssl ecparam -name prime256v1 -genkey -noout -out eckey.pem 19 | ``` 20 | This will create the file `eckey.pem`. *Do not publish this file!* 21 | 22 | ## Create a Server-to-Server key in CloudKit Dashboard 23 | 24 | In [CloudKit Dashboard](https://icloud.developer.apple.com/dashboard) select the CocoaHeadsNL container and navigate to 25 | `API Access -> Server-to-Server Keys`. Copy the public key in the output of this command: 26 | ``` 27 | openssl ec -in eckey.pem -pubout 28 | ``` 29 | 30 | and paste it into the *Public Key* text field of the new key. Hit *Save* and the *Key ID* attribute will get populated. 31 | Copy this ID and fill in the **keyID** property in `config.js`. 32 | 33 | ## Make sure all placeholders in config.js are replaced with valid values. 34 | 35 | - github username 36 | - github access token 37 | - Meetup API Key 38 | - Slack Hook URL 39 | - Server-2-server CloudKit key 40 | 41 | See config.js for more information. 42 | 43 | ## Run the script 44 | 45 | ``` 46 | node index.js 47 | ``` 48 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CloudKitSync/jobs/loadJobInfo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var xmlreader = require('./xmlreader.js'); 3 | var Promise = require('promise'); 4 | var request = require('request'); 5 | var config = require('../config'); 6 | 7 | exports.load = function() { 8 | var loadJobInfoPromise = new Promise(function(resolve, reject) { 9 | var options = { 10 | url: 'http://jobs.cocoaheads.nl/feed.xml', 11 | } 12 | 13 | request(options, function(error, response, body){ 14 | if (error) { 15 | reject(error); 16 | console.log("Error on fetching Job information: " + error) 17 | return; 18 | } 19 | 20 | if (response.statusCode != 200) { 21 | reject(response.statusCode); 22 | console.log("Invalid HTTP status code on Job information: " + response.statusCode) 23 | return; 24 | } 25 | 26 | var jobPostings = [] 27 | 28 | xmlreader.read(body, function (err, res){ 29 | if (err) { 30 | reject(err); 31 | return; 32 | } 33 | var rssChannel = res.rss.channel; 34 | rssChannel.item.each(function (i, newJobItem){ 35 | var jobPosting = {recordType: 'Job', 36 | fields: { 37 | link: {value: newJobItem.link.text()}, 38 | title: {value: newJobItem.title.text()}, 39 | content: {value: newJobItem.description.text()}, 40 | date: {value: new Date(newJobItem.pubDate.text()).getTime()}, 41 | logoUrl: {value: newJobItem['atom:link'].attributes()['href']} 42 | } 43 | } 44 | 45 | jobPostings.push(jobPosting) 46 | }); 47 | }); 48 | 49 | resolve(jobPostings); 50 | }); 51 | }); 52 | 53 | return loadJobInfoPromise; 54 | } 55 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL.xcodeproj/project.xcworkspace/xcshareddata/CocoaHeadsNL.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | C5835790-E370-482D-B683-43C2E8CA66B3 9 | IDESourceControlProjectName 10 | CocoaHeadsNL 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 51DBBF3565E407743FF3E195305ECEEB9BD05414 14 | ssh://bitbucket.org/cocoaheadsnl/cocoaheadsnl.git 15 | 16 | IDESourceControlProjectPath 17 | CocoaHeadsNL/CocoaHeadsNL.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 51DBBF3565E407743FF3E195305ECEEB9BD05414 21 | ../../.. 22 | 23 | IDESourceControlProjectURL 24 | ssh://bitbucket.org/cocoaheadsnl/cocoaheadsnl.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 51DBBF3565E407743FF3E195305ECEEB9BD05414 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 51DBBF3565E407743FF3E195305ECEEB9BD05414 36 | IDESourceControlWCCName 37 | cocoaheadsnl 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /CocoaHeadsNL/itemNotification/NotificationViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationViewController.swift 3 | // itemNotification 4 | // 5 | // Created by Bart Hoffman on 10/03/2018. 6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import UserNotifications 11 | import UserNotificationsUI 12 | import CloudKit 13 | 14 | class NotificationViewController: UIViewController, UNNotificationContentExtension { 15 | 16 | @IBOutlet var titlelabel: UILabel? 17 | @IBOutlet var nameLabel: UILabel? 18 | @IBOutlet var postImageView: UIImageView? 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | // Do any required interface initialization here. 23 | } 24 | 25 | func didReceive(_ notification: UNNotification) { 26 | 27 | if let userInfo = notification.request.content.userInfo as NSDictionary as? [String: Any] { 28 | let query = CKQueryNotification(fromRemoteNotificationDictionary: userInfo)! 29 | 30 | if let title = query.recordFields?["title"] as? String, 31 | let name = query.recordFields?["name"] as? String { 32 | self.titlelabel?.text = title 33 | self.nameLabel?.text = name 34 | } 35 | if let attachment = notification.request.content.attachments.first { 36 | if attachment.url.startAccessingSecurityScopedResource() { 37 | if let data = NSData(contentsOfFile: attachment.url.path) as Data? { 38 | self.postImageView?.image = UIImage(data: data) 39 | attachment.url.stopAccessingSecurityScopedResource() 40 | } 41 | } 42 | } 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/ButtonCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonCell.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Jeroen Leenarts on 29-09-16. 6 | // Copyright © 2016 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ButtonCell: UITableViewCell { 12 | 13 | @IBOutlet weak var titleButton: UIButton! 14 | @IBAction func buttonPressed(_ sender: AnyObject) { 15 | if let url = selectURL() { 16 | UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: { _ in 17 | }) 18 | } 19 | } 20 | 21 | fileprivate func selectURL() -> URL? { 22 | guard let urlString = urlString else { 23 | return nil 24 | } 25 | 26 | guard let url = URL(string: urlString) else { 27 | return nil 28 | } 29 | 30 | let appUrlString = "meetup:/\(url.path)" 31 | 32 | let appUrl = URL(string: appUrlString) 33 | 34 | if appUrl != nil && UIApplication.shared.canOpenURL(appUrl!) { 35 | return appUrl 36 | } else if UIApplication.shared.canOpenURL(url) { 37 | return url 38 | } else { 39 | return nil 40 | } 41 | } 42 | 43 | var title: String? { 44 | didSet { 45 | self.titleButton.setTitle(title, for: .normal) 46 | } 47 | } 48 | 49 | var urlString: String? 50 | } 51 | 52 | extension ButtonCell: Identifiable {} 53 | 54 | // Helper function inserted by Swift 4.2 migrator. 55 | private func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] { 56 | return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value) }) 57 | } 58 | -------------------------------------------------------------------------------- /provisioning/import_provisioning.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Decrypt the files 4 | # --batch to prevent interactive command --yes to assume "yes" for questions 5 | gpg --quiet --batch --yes --decrypt --passphrase="$PROVISIONING_PASSWORD" --output provisioning/AppStoreCertificates.p12 provisioning/AppStoreCertificates.p12.gpg 6 | gpg --quiet --batch --yes --decrypt --passphrase="$PROVISIONING_PASSWORD" --output provisioning/CocoaHeadsNL-AppStore-General-Notification.mobileprovision provisioning/CocoaHeadsNL-AppStore-General-Notification.mobileprovision.gpg 7 | gpg --quiet --batch --yes --decrypt --passphrase="$PROVISIONING_PASSWORD" --output provisioning/CocoaHeadsNL-AppStore-Item-Notification.mobileprovision provisioning/CocoaHeadsNL-AppStore-Item-Notification.mobileprovision.gpg 8 | gpg --quiet --batch --yes --decrypt --passphrase="$PROVISIONING_PASSWORD" --output provisioning/CocoaHeadsNL-AppStore-Item-Service-Extension.mobileprovision provisioning/CocoaHeadsNL-AppStore-Item-Service-Extension.mobileprovision.gpg 9 | gpg --quiet --batch --yes --decrypt --passphrase="$PROVISIONING_PASSWORD" --output provisioning/CocoaHeadsNL-AppStore.mobileprovision provisioning/CocoaHeadsNL-AppStore.mobileprovision.gpg 10 | 11 | mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles 12 | 13 | echo "List profiles" 14 | ls ~/Library/MobileDevice/Provisioning\ Profiles/ 15 | echo "Move profiles" 16 | cp provisioning/*.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/ 17 | echo "List profiles" 18 | ls ~/Library/MobileDevice/Provisioning\ Profiles/ 19 | 20 | security create-keychain -p "" build.keychain 21 | security import provisioning/AppStoreCertificates.p12 -t agg -k ~/Library/Keychains/build.keychain -P "$PROVISIONING_PASSWORD" -A 22 | 23 | security list-keychains -s ~/Library/Keychains/build.keychain 24 | security default-keychain -s ~/Library/Keychains/build.keychain 25 | security unlock-keychain -p "" ~/Library/Keychains/build.keychain 26 | security set-key-partition-list -S apple-tool:,apple: -s -k "" ~/Library/Keychains/build.keychain 27 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/CoreLocationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreLocationController.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Bart Hoffman on 23/08/15. 6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreLocation 11 | 12 | class CoreLocationController: NSObject, CLLocationManagerDelegate { 13 | 14 | let locationManager = CLLocationManager() 15 | let locationNotification = "LOCATION_AVAILABLE" 16 | 17 | override init() { 18 | super.init() 19 | 20 | self.locationManager.delegate = self 21 | self.locationManager.distanceFilter = 5000 22 | self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer 23 | self.locationManager.requestWhenInUseAuthorization() 24 | } 25 | 26 | // MARK: - CLLocationManagerDelegate methods 27 | 28 | func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { 29 | print("didChangeAuthorizationStatus") 30 | 31 | switch status { 32 | case .notDetermined: 33 | print(".NotDetermined") 34 | case .authorizedAlways: 35 | print(".Authorized") 36 | case .authorizedWhenInUse: 37 | print(".AuthorizedWhenInUse") 38 | self.locationManager.startUpdatingLocation() 39 | case .denied: 40 | print(".Denied") 41 | default: 42 | print("Unhandled authorization status") 43 | } 44 | } 45 | 46 | func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { 47 | 48 | let location = locations.last! 49 | 50 | print("didUpdateLocations: \(location.coordinate.latitude), \(location.coordinate.longitude)") 51 | 52 | let userInfo = [ "location": location] 53 | 54 | let notificationCenter = NotificationCenter.default 55 | notificationCenter.post(name: Notification.Name(rawValue: locationNotification), object: nil, userInfo: userInfo) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/JobsCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JobsCell.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Bart Hoffman on 10/03/16. 6 | // Copyright © 2016 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class JobsCell: UICollectionViewCell { 12 | 13 | @IBOutlet weak var textLabel: UILabel! 14 | @IBOutlet weak var imageView: UIImageView! 15 | @IBOutlet weak var separator: UIView! 16 | @IBOutlet weak var container: UIView! 17 | 18 | fileprivate var imageLoaded = false 19 | 20 | var job: Job? { 21 | didSet { 22 | self.imageView.image = job?.logoImage 23 | self.textLabel.text = job?.title 24 | 25 | if let title = job?.title { 26 | 27 | let accessibilityLabel: String! 28 | if let companyName = job?.companyName { 29 | let text = NSLocalizedString("%1$@ at %2$@") 30 | accessibilityLabel = String(format: text, title, companyName) 31 | } else { 32 | accessibilityLabel = title 33 | } 34 | container.accessibilityLabel = accessibilityLabel 35 | container.accessibilityHint = NSLocalizedString("Double-tap for more details.") 36 | } 37 | } 38 | } 39 | 40 | var rightHandSide = false { 41 | didSet { 42 | self.separator.isHidden = rightHandSide 43 | } 44 | } 45 | 46 | override func awakeFromNib() { 47 | super.awakeFromNib() 48 | 49 | // Show a darker background when the cell is tapped (just like the 50 | // table view cells in the Meetups tab). 51 | let selectedView = UIView() 52 | selectedView.backgroundColor = UIColor(red: 0.85, green: 0.85, blue: 0.85, alpha: 1) 53 | self.selectedBackgroundView = selectedView 54 | } 55 | 56 | override func prepareForReuse() { 57 | super.prepareForReuse() 58 | 59 | self.textLabel.text = "" 60 | //self.imageView.image = nil 61 | } 62 | } 63 | 64 | extension JobsCell: Identifiable {} 65 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/CompanyDataSource.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import StoreKit 3 | import CloudKit 4 | 5 | class CompanyDataSource: DetailDataSource { 6 | weak var presenter: DetailViewController? 7 | 8 | var company: Company { 9 | return object as! Company // swiftlint:disable:this force_cast 10 | } 11 | 12 | override var title: String? { 13 | return company.name 14 | } 15 | 16 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 17 | switch section { 18 | case 1: 19 | return NSLocalizedString("Apps", comment: "Section title for app section in company details.") 20 | default: 21 | return nil 22 | } 23 | } 24 | 25 | override func numberOfSections(in tableView: UITableView) -> Int { 26 | return 1 27 | } 28 | 29 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 30 | return 4 31 | } 32 | 33 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 34 | switch (indexPath as NSIndexPath).row { 35 | case 0: 36 | return logoCellWithFile(company.logoImage, forTableView: tableView, forRowAt: indexPath) 37 | case 1: 38 | return titleCellWithText(company.emailAddress, forTableView: tableView, forRowAt: indexPath) 39 | case 2: 40 | return titleCellWithText(company.streetAddress, forTableView: tableView, forRowAt: indexPath) 41 | case 3: 42 | return titleCellWithText(company.zipCode, forTableView: tableView, forRowAt: indexPath) 43 | default: 44 | fatalError("This should not happen.") 45 | } 46 | } 47 | 48 | func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 49 | if section == 1 { 50 | tableView.headerView(forSection: 1)?.backgroundColor = UIColor.gray 51 | return tableView.headerView(forSection: 1) 52 | } else { 53 | return UIView(frame: CGRect.zero) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://build.appcenter.ms/v0.1/apps/b268db8e-5e09-40fb-9c8e-bd30aac6080a/branches/master/badge)](https://appcenter.ms) 2 | 3 | # About this app 4 | 5 | **Why an app?** We're a community of mostly mobile developers... so could you imagine we wouldn't? 6 | 7 | **Why open source it?** We are a non-profit organisation and organise our monthly meetup to share ideas, learn from each other and meet other developers. Keeping it closed seemed out of place. 8 | 9 | **How do we see this working?** We used an [open source license](LICENSE.md) to promote sharing for non-commercial use and educational purposes. 10 | 11 | In case you have any ideas, suggestions or additions, just get in contact so you can see what is happening already. We still got some things on our wishlist. 12 | 13 | Our email: [foundation@cocoaheads.nl](mailto:foundation@cocoaheads.nl) 14 | 15 | 16 | ## Project structure 17 | 18 | CocoaHeadsNL consists of a single project. 19 | 20 | Minimal deployment target of the app is iOS 12.0 21 | 22 | ## Contributions 23 | 24 | Please note that we consider all ownership of contributions made to this project to automatically transfer to Stichting CocoaHeadsNL. If you do not agree to this, do not contribute. When we conclude that you have an interest in contributing to this project, we will ask you to confirm transfer of ownership of you contributions in writing. 25 | 26 | Also note, any contributions we receive should be allowed to be transferred to Stichting CocoaHeadsNL. If you make contributions and at a later stage ownership of said contributions are not lawfully transfered to our possesion, you as a contributor are considered liable for this. 27 | 28 | The above statement may sound harsh, but basically if you write original code on your own or legally alloted time and use open source components with compatible licenses: You are perfectly fine. 29 | 30 | As maintainers of this project we do actively try and guide contributors through these hurdles, we want to work with our community to make this project a great success. 31 | 32 | All contributors to this project are listed here: [contributors](https://github.com/CocoaHeadsNL/CocoaHeadsNL-iOS/graphs/contributors) 33 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/NSManagedObjectContext+AsyncHelpers.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | import Swift 3 | 4 | extension NSManagedObjectContext { 5 | /** 6 | Synchronously executes a given function on the receiver’s queue. 7 | You use this method to safely address managed objects on a concurrent 8 | queue. 9 | - attention: This method may safely be called reentrantly. 10 | - parameter body: The method body to perform on the reciever. 11 | - returns: The value returned from the inner function. 12 | - throws: Any error thrown by the inner function. This method should be 13 | technically `rethrows`, but cannot be due to Swift limitations. 14 | **/ 15 | public func performAndWaitOrThrow(_ body: () throws -> Return) rethrows -> Return { 16 | #if swift(>=3.1) 17 | return try withoutActuallyEscaping(body) { work in 18 | var result: Return! 19 | var error: Error? 20 | 21 | performAndWait { 22 | do { 23 | result = try work() 24 | } catch let workError { 25 | error = workError 26 | } 27 | } 28 | 29 | if let error = error { 30 | throw error 31 | } else { 32 | return result 33 | } 34 | } 35 | #else 36 | func impl(execute work: () throws -> Return, recover: (Error) throws -> Void) rethrows -> Return { 37 | var result: Return! 38 | var error: Error? 39 | 40 | // performAndWait is marked @escaping as of iOS 10.0. 41 | typealias Fun = (() -> Void) -> Void // swiftlint:disable:this nesting 42 | let performAndWaitNoescape = unsafeBitCast(self.performAndWait, to: Fun.self) 43 | performAndWaitNoescape { 44 | do { 45 | result = try work() 46 | } catch let workError { 47 | error = workError 48 | } 49 | } 50 | 51 | if let error = error { 52 | try recover(error) 53 | } 54 | 55 | return result 56 | } 57 | 58 | return try impl(execute: body, recover: { throw $0 }) 59 | #endif 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CloudKitSync/jobs/loadContributorInfo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Promise = require('promise'); 4 | var request = require('request'); 5 | var config = require('../config'); 6 | 7 | 8 | 9 | exports.load = function() { 10 | var loadContributorsPromise = new Promise(function(resolve, reject) { 11 | 12 | 13 | var options = { 14 | url: 'https://api.github.com/repos/CocoaHeadsNL/CocoaHeadsNL-iOS/stats/contributors', 15 | headers: { 'User-Agent': 'CocoaHeadsNL-Cloud-Sync'}, 16 | auth: config.githubAuthOptions 17 | } 18 | 19 | request(options, function(error, response, body){ 20 | if (error) { 21 | reject(error); 22 | console.log("Error on fetching contributors: " + error) 23 | return; 24 | } 25 | 26 | if (response.statusCode != 200) { 27 | reject(response.statusCode); 28 | console.log("Invalid HTTP status code on fetching contributors: " + response.statusCode) 29 | return; 30 | } 31 | resolve(body); 32 | }); 33 | }).then(function(body) { 34 | var contributorData = JSON.parse(body); 35 | 36 | var promises = [] 37 | var authorDetails = [] 38 | 39 | contributorData.forEach(function(contributorInfo) { 40 | var detailPromise = new Promise(function(resolve, reject) { 41 | var author = contributorInfo["author"]; 42 | request({url: author.url,headers: { 'User-Agent': 'CocoaHeadsNL-Cloud-Sync'}, auth: config.githubAuthOptions}, function(error, response, body){ 43 | if (error) { 44 | console.log("Error on fetching contributor details: " + error) 45 | reject(error); 46 | return; 47 | } 48 | 49 | if (response.statusCode != 200) { 50 | console.log("Invalid HTTP status code on fetching contributor details: " + response.statusCode) 51 | reject(response.statusCode); 52 | return; 53 | } 54 | 55 | var authorDetailData = JSON.parse(body) 56 | authorDetailData.commit_count = contributorInfo.total 57 | authorDetails.push(authorDetailData) 58 | resolve(authorDetailData) 59 | }) 60 | }) 61 | 62 | promises.push(detailPromise) 63 | }) 64 | 65 | return Promise.all(promises) 66 | }) 67 | 68 | return loadContributorsPromise 69 | } 70 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/MapViewCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import MapKit 3 | 4 | class MapViewCell: UITableViewCell, MKMapViewDelegate { 5 | @IBOutlet weak var littleMap: MKMapView! 6 | 7 | var geoLocation: CLLocation? { 8 | didSet { 9 | if geoLocation != oldValue { 10 | let mapRegion = MKCoordinateRegion(center: self.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)) 11 | littleMap.region = mapRegion 12 | } 13 | } 14 | } 15 | 16 | var locationName: String? { 17 | didSet { 18 | if let locationName = locationName, locationName != oldValue { 19 | let annotation = MapAnnotation(coordinate: self.coordinate, title: NSLocalizedString("Here it is!"), subtitle: locationName) 20 | littleMap.addAnnotation(annotation) 21 | } 22 | } 23 | } 24 | 25 | fileprivate var coordinate: CLLocationCoordinate2D { 26 | if let geoLocation = geoLocation { 27 | return CLLocationCoordinate2D(latitude: geoLocation.coordinate.latitude, longitude: geoLocation.coordinate.longitude) 28 | } else { 29 | return CLLocationCoordinate2D(latitude: 0, longitude: 0) 30 | } 31 | } 32 | 33 | func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { 34 | let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pinAnnotationView") 35 | annotationView.animatesDrop = true 36 | return annotationView 37 | } 38 | 39 | func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { 40 | self.openMapWithCoordinate(coordinate) 41 | } 42 | 43 | func openMapWithCoordinate(_ coordinate: CLLocationCoordinate2D? = nil) { 44 | let placemark = MKPlacemark(coordinate: coordinate ?? self.coordinate, addressDictionary: nil) 45 | let mapItem = MKMapItem(placemark: placemark) 46 | 47 | if let locationName = locationName { 48 | mapItem.name = locationName 49 | } 50 | 51 | let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving] 52 | let currentLocationMapItem = MKMapItem.forCurrentLocation() 53 | 54 | MKMapItem.openMaps(with: [currentLocationMapItem, mapItem], launchOptions: launchOptions) 55 | } 56 | } 57 | 58 | extension MapViewCell: Identifiable {} 59 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/MeetupDataSource.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import MapKit 3 | 4 | class MeetupDataSource: DetailDataSource { 5 | var meetup: Meetup { 6 | return object as! Meetup // swiftlint:disable:this force_cast 7 | } 8 | 9 | override var title: String? { 10 | return meetup.name 11 | } 12 | 13 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 14 | return 7 15 | } 16 | 17 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 18 | switch indexPath.row { 19 | case 1: 20 | if let mapCell = tableView.cellForRow(at: indexPath) as? MapViewCell { 21 | mapCell.openMapWithCoordinate() 22 | } 23 | tableView.deselectRow(at: indexPath, animated: true) 24 | default: 25 | tableView.deselectRow(at: indexPath, animated: true) 26 | } 27 | 28 | } 29 | 30 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 31 | switch indexPath.row { 32 | case 0: 33 | return logoCellWithFile(meetup.logoImage, forTableView: tableView, forRowAt: indexPath) 34 | case 1: 35 | if meetup.latitude == 0.0 && meetup.longitude == 0.0 { 36 | return onlineCell(forRowAt: indexPath) 37 | } else { 38 | let geoLocation = CLLocation(latitude: meetup.latitude, longitude: meetup.longitude) 39 | return mapViewCellWithLocation(geoLocation, name: meetup.locationName, forTableView: tableView, forRowAt: indexPath) 40 | } 41 | case 2: 42 | return titleCellWithText(meetup.name, forTableView: tableView, forRowAt: indexPath) 43 | case 3: 44 | let text = String("Number of Cocoaheads: \(meetup.yesRsvpCount)") 45 | return titleCellWithText(text, forTableView: tableView, forRowAt: indexPath) 46 | case 4: 47 | return titleCellWithDate(meetup.time, forTableView: tableView, forRowAt: indexPath) 48 | case 5: 49 | return buttonCell(meetup.meetupUrl, title: "Open Meetup", forTableView: tableView, forRowAt: indexPath) 50 | case 6: 51 | return dataCellWithHTML(meetup.meetupDescription, forTableView: tableView, forRowAt: indexPath) 52 | default: 53 | fatalError("This should not happen.") 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/UITableView+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+Extensions.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Jeroen Leenarts on 10/01/2020. 6 | // Copyright © 2020 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UICollectionView { 12 | 13 | func register(type: T.Type, ofKind kind: String, prefix: String = "") where T: UICollectionReusableView { 14 | register(type, forSupplementaryViewOfKind: kind, withReuseIdentifier: prefix + type.identifier) 15 | } 16 | 17 | func registerNib(type: T.Type, prefix: String = "") where T: UICollectionViewCell { 18 | register(UINib(nibName: type.identifier, bundle: nil), forCellWithReuseIdentifier: prefix + type.identifier) 19 | } 20 | 21 | func dequeueReusableSupplementaryView(ofKind kind: String, type: T.Type, for indexPath: IndexPath, prefix: String = "") -> T where T: UICollectionReusableView { 22 | 23 | return dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: prefix + type.identifier, for: indexPath) as! T // swiftlint:disable:this force_cast 24 | } 25 | 26 | func dequeueReusableCell(type: T.Type, for indexPath: IndexPath, prefix: String = "") -> T where T: UICollectionViewCell { 27 | return dequeueReusableCell(withReuseIdentifier: prefix + type.identifier, for: indexPath) as! T // swiftlint:disable:this force_cast 28 | } 29 | } 30 | 31 | extension UITableView { 32 | 33 | func register(type: T.Type, prefix: String = "") where T: UITableViewCell { 34 | register(type, forCellReuseIdentifier: prefix + type.identifier) 35 | } 36 | 37 | func dequeueReusableCell(type: T.Type, for indexPath: IndexPath, prefix: String = "") -> T where T: UITableViewCell { 38 | return dequeueReusableCell(withIdentifier: prefix + type.identifier, for: indexPath) as! T // swiftlint:disable:this force_cast 39 | } 40 | 41 | func registerNib(type: T.Type, prefix: String = "") where T: UITableViewCell { 42 | let nib = UINib(nibName: prefix + type.identifier, bundle: nil) 43 | register(nib, forCellReuseIdentifier: prefix + type.identifier) 44 | } 45 | 46 | func registerNib(type: T.Type, prefix: String = "") where T: UITableViewHeaderFooterView { 47 | let nib = UINib(nibName: prefix + type.identifier, bundle: nil) 48 | register(nib, forHeaderFooterViewReuseIdentifier: prefix + type.identifier) 49 | } 50 | 51 | func dequeueReusableHeaderFooterView(type: T.Type, prefix: String = "") -> T where T: UITableViewHeaderFooterView { 52 | return dequeueReusableHeaderFooterView(withIdentifier: prefix + type.identifier) as! T // swiftlint:disable:this force_cast 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/CalendarTabImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CalendarTabImage.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Bruno Scheele on 29/05/15. 6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIImage { 13 | class func calendarTabImageWithCurrentDate() -> UIImage { 14 | return calendarTabImageWithDate(Date()) 15 | } 16 | 17 | class func calendarTabImageWithDate(_ date: Date) -> UIImage { 18 | let dateFormatter = DateFormatter() 19 | let view = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) 20 | let maskView = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) 21 | 22 | let calendarView = UIImageView() 23 | calendarView.frame = CGRect(x: 0, y: 0, width: 30, height: 30) 24 | calendarView.image = UIImage(named: "EventsTabIcon") 25 | view.addSubview(calendarView) 26 | 27 | // Create the month 28 | 29 | dateFormatter.dateFormat = "MMM" 30 | let month = dateFormatter.string(from: date).uppercased() 31 | 32 | let monthLabel = UILabel(frame: CGRect(x: 0, y: 4, width: 30, height: 6)) 33 | monthLabel.text = month 34 | monthLabel.font = UIFont.boldSystemFont(ofSize: 6) 35 | monthLabel.textAlignment = .center 36 | monthLabel.textColor = UIColor.white 37 | maskView.addSubview(monthLabel) 38 | 39 | // Create the day number 40 | 41 | dateFormatter.dateFormat = "dd" 42 | let day = dateFormatter.string(from: date) 43 | 44 | let numberLabel = UILabel(frame: CGRect(x: 0, y: 10, width: 30, height: 17)) 45 | numberLabel.text = day 46 | numberLabel.font = UIFont.boldSystemFont(ofSize: 14) 47 | numberLabel.textAlignment = .center 48 | view.addSubview(numberLabel) 49 | 50 | UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, UIScreen.main.scale) 51 | view.layer.render(in: UIGraphicsGetCurrentContext()!) 52 | let image = UIGraphicsGetImageFromCurrentImageContext() 53 | UIGraphicsEndImageContext() 54 | 55 | UIGraphicsBeginImageContextWithOptions(maskView.bounds.size, false, UIScreen.main.scale) 56 | maskView.layer.render(in: UIGraphicsGetCurrentContext()!) 57 | let maskingImage = UIGraphicsGetImageFromCurrentImageContext() 58 | UIGraphicsEndImageContext() 59 | 60 | // Mask month out of the image to show in tabBar. 61 | let resultImage = image?.maskImage(maskingImage!) 62 | return resultImage! 63 | } 64 | 65 | func maskImage(_ maskImage: UIImage) -> UIImage { 66 | let maskRef = maskImage.cgImage 67 | 68 | let mask = CGImage(maskWidth: (maskRef?.width)!, height: (maskRef?.height)!, bitsPerComponent: (maskRef?.bitsPerComponent)!, bitsPerPixel: (maskRef?.bitsPerPixel)!, bytesPerRow: (maskRef?.bytesPerRow)!, provider: (maskRef?.dataProvider!)!, decode: nil, shouldInterpolate: false) 69 | 70 | let masked = self.cgImage?.masking(mask!)! 71 | let maskedImage = UIImage(cgImage: masked!, scale: UIScreen.main.scale, orientation: .up) 72 | 73 | return maskedImage 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/DetailDataSource.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import CloudKit 3 | 4 | /** 5 | * Base class for all data sources used by the Detail screen. 6 | * It has convenience methods for making specific types of cells. 7 | */ 8 | class DetailDataSource: NSObject, UITableViewDataSource, UITableViewDelegate { 9 | let object: AnyObject 10 | var tableView: UITableView! 11 | 12 | init(object: AnyObject) { 13 | self.object = object 14 | } 15 | 16 | var title: String? { 17 | return "" 18 | } 19 | 20 | func numberOfSections(in tableView: UITableView) -> Int { 21 | return 1 22 | } 23 | 24 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 25 | return 1 26 | } 27 | 28 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 29 | fatalError("Subclass must implement this") 30 | } 31 | 32 | func logoCellWithFile(_ logo: UIImage, forTableView tableView: UITableView, forRowAt indexPath: IndexPath) -> LogoCell { 33 | let cell = tableView.dequeueReusableCell(type: LogoCell.self, for: indexPath) 34 | cell.logoImageView.image = logo 35 | cell.logoImageView.contentMode = .scaleAspectFit 36 | return cell 37 | } 38 | 39 | func onlineCell(forRowAt indexPath: IndexPath) -> OnlineCell { 40 | let cell = tableView.dequeueReusableCell(type: OnlineCell.self, for: indexPath) 41 | return cell 42 | } 43 | 44 | func mapViewCellWithLocation(_ location: CLLocation?, name: String?, forTableView tableView: UITableView, forRowAt indexPath: IndexPath) -> MapViewCell { 45 | let cell = tableView.dequeueReusableCell(type: MapViewCell.self, for: indexPath) 46 | cell.geoLocation = location 47 | cell.locationName = name 48 | return cell 49 | } 50 | 51 | func titleCellWithText(_ text: String?, forTableView tableView: UITableView, forRowAt indexPath: IndexPath) -> TitleCell { 52 | let cell = tableView.dequeueReusableCell(type: TitleCell.self, for: indexPath) 53 | cell.content = text 54 | return cell 55 | } 56 | 57 | func titleCellWithDate(_ date: Date?, forTableView tableView: UITableView, forRowAt indexPath: IndexPath) -> TitleCell { 58 | let cell = tableView.dequeueReusableCell(type: TitleCell.self, for: indexPath) 59 | cell.date = date 60 | return cell 61 | } 62 | 63 | func dataCellWithHTML(_ html: String?, forTableView tableView: UITableView, forRowAt indexPath: IndexPath) -> HTMLDataCell { 64 | let cell = tableView.dequeueReusableCell(type: HTMLDataCell.self, for: indexPath) 65 | cell.html = html 66 | return cell 67 | } 68 | 69 | func buttonCell(_ urlString: String?, title: String, forTableView tableView: UITableView, forRowAt indexPath: IndexPath) -> ButtonCell { 70 | let cell = tableView.dequeueReusableCell(type: ButtonCell.self, for: indexPath) 71 | cell.title = title 72 | cell.urlString = urlString 73 | return cell 74 | } 75 | 76 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 77 | return UITableView.automaticDimension 78 | } 79 | 80 | func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { 81 | return CGFloat(88) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /CocoaHeadsNL/ItemServiceExtension/NotificationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationService.swift 3 | // ItemServiceExtension 4 | // 5 | // Created by Bart Hoffman on 11/03/2018. 6 | // Copyright © 2018 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import UserNotifications 10 | import CloudKit 11 | 12 | class NotificationService: UNNotificationServiceExtension { 13 | 14 | var contentHandler: ((UNNotificationContent) -> Void)? 15 | var bestAttemptContent: UNMutableNotificationContent? 16 | 17 | override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { 18 | 19 | self.contentHandler = contentHandler 20 | bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) 21 | 22 | if let bestAttemptContent = bestAttemptContent { 23 | 24 | if let userInfo = request.content.userInfo as NSDictionary as? [String: Any] { 25 | let query = CKQueryNotification(fromRemoteNotificationDictionary: userInfo)! 26 | 27 | if let title = query.recordFields?["title"] as? String, 28 | let name = query.recordFields?["name"] as? String, 29 | let imageUrl = query.recordFields?["imageUrl"] as? String { 30 | 31 | bestAttemptContent.title = title 32 | bestAttemptContent.body = name 33 | 34 | if let fileUrl = URL(string: imageUrl) { 35 | 36 | //let request = NSURLRequest(url: fileUrl) 37 | URLSession.shared.downloadTask(with: fileUrl, completionHandler: { fileLocation, _, err in 38 | 39 | //create attachment from file at url in folder 40 | if let location = fileLocation, err == nil { 41 | let tmpDirectory = NSTemporaryDirectory() 42 | let tmpFile = "file:".appending(tmpDirectory).appending(fileUrl.lastPathComponent) 43 | let tmpUrl = URL(string: tmpFile)! 44 | 45 | do { 46 | try? FileManager.default.copyItem(at: location, to: tmpUrl) 47 | 48 | if let attachment = try? UNNotificationAttachment(identifier: "", url: tmpUrl, options: nil) { 49 | self.bestAttemptContent?.attachments = [attachment] 50 | contentHandler(bestAttemptContent) 51 | } 52 | } 53 | } 54 | }) .resume() 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | override func serviceExtensionTimeWillExpire() { 62 | // Called just before the extension will be terminated by the system. 63 | // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. 64 | if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { 65 | contentHandler(bestAttemptContent) 66 | } 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /CocoaHeadsNL/itemNotification/Base.lproj/MainInterface.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNLUITests/CocoaHeadsNLUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaHeadsNLUITests.swift 3 | // CocoaHeadsNLUITests 4 | // 5 | // Created by Jeroen Leenarts on 25-05-16. 6 | // Copyright © 2016 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class CocoaHeadsNLUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | let app = XCUIApplication() 17 | app.launch() 18 | } 19 | 20 | override func tearDown() { 21 | // Put teardown code here. This method is called after the invocation of each test method in the class. 22 | super.tearDown() 23 | } 24 | 25 | func testApp() { 26 | let app = XCUIApplication() 27 | 28 | waitForHittable(app.tables.staticTexts["CocoaHeads rules"], waitSeconds: 10) 29 | 30 | app.tables.cells.element(boundBy: 0).tap() 31 | 32 | app.tabBars.buttons["Jobs"].tap() 33 | waitForNotHittable(app.activityIndicators.element(boundBy: 0), waitSeconds: 10) 34 | 35 | app.tabBars.buttons["About"].tap() 36 | waitForNotHittable(app.activityIndicators.element(boundBy: 0), waitSeconds: 10) 37 | } 38 | 39 | } 40 | 41 | extension XCTestCase { 42 | func waitForHittable(_ element: XCUIElement, waitSeconds: Double, file: String = #file, line: UInt = #line) { 43 | let existsPredicate = NSPredicate(format: "hittable == true") 44 | expectation(for: existsPredicate, evaluatedWith: element, handler: nil) 45 | 46 | waitForExpectations(timeout: waitSeconds) { error -> Void in 47 | if error != nil { 48 | //let message = "Failed to find \(element) after \(waitSeconds) seconds." 49 | //self.recordFailure(withDescription: message, inFile: file, atLine: Int(line), expected: true) 50 | let issue = XCTIssue(type: .assertionFailure, compactDescription: "Failed to find \(element) after \(waitSeconds) seconds.") 51 | self.record(issue) 52 | } 53 | } 54 | } 55 | 56 | func waitForNotHittable(_ element: XCUIElement, waitSeconds: Double, file: String = #file, line: UInt = #line) { 57 | let existsPredicate = NSPredicate(format: "hittable == false") 58 | expectation(for: existsPredicate, evaluatedWith: element, handler: nil) 59 | 60 | waitForExpectations(timeout: waitSeconds) { error -> Void in 61 | if error != nil { 62 | //let message = "Failed to find \(element) after \(waitSeconds) seconds." 63 | //self.recordFailure(withDescription: message, inFile: file, atLine: Int(line), expected: true) 64 | let issue = XCTIssue(type: .assertionFailure, compactDescription: "Failed to find \(element) after \(waitSeconds) seconds.") 65 | self.record(issue) 66 | } 67 | } 68 | } 69 | 70 | func waitForExists(_ element: XCUIElement, waitSeconds: Double, file: String = #file, line: UInt = #line) { 71 | let existsPredicate = NSPredicate(format: "exists == true") 72 | expectation(for: existsPredicate, evaluatedWith: element, handler: nil) 73 | 74 | waitForExpectations(timeout: waitSeconds) { error -> Void in 75 | if error != nil { 76 | //let message = "Failed to find \(element) after \(waitSeconds) seconds." 77 | //self.recordFailure(withDescription: message, inFile: file, atLine: Int(line), expected: true) 78 | let issue = XCTIssue(type: .assertionFailure, compactDescription: "Failed to find \(element) after \(waitSeconds) seconds.") 79 | self.record(issue) 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/SplitViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplitViewController.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Bart Hoffman on 14/03/15. 6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class SplitViewController: UISplitViewController, UISplitViewControllerDelegate { 13 | override func viewDidLoad() { 14 | self.delegate = self 15 | self.preferredDisplayMode = UISplitViewController.DisplayMode.allVisible 16 | 17 | NotificationCenter.default.addObserver(self, selector: #selector(SplitViewController.searchNotification(_:)), name: NSNotification.Name(rawValue: searchNotificationName), object: nil) 18 | 19 | //Inspect paste board for userInfo 20 | if let pasteBoard = UIPasteboard(name: UIPasteboard.Name(rawValue: searchPasteboardName), create: false) { 21 | let uniqueIdentifier = pasteBoard.string 22 | if let components = uniqueIdentifier?.components(separatedBy: ":") { 23 | if !components.isEmpty { 24 | let type = components[0] 25 | displayTabForType(type) 26 | } 27 | } 28 | } 29 | } 30 | @objc func searchNotification(_ notification: Notification) { 31 | guard let userInfo = (notification as NSNotification).userInfo as? [String: String] else { 32 | return 33 | } 34 | 35 | if let type = userInfo["type"] { 36 | displayTabForType(type) 37 | } 38 | } 39 | 40 | func displayTabForType(_ type: String) { 41 | guard let tabBarController = self.viewControllers[0] as? UITabBarController else { 42 | return 43 | } 44 | 45 | if type == "meetup" { 46 | tabBarController.selectedIndex = 0 47 | } else if type == "job" { 48 | tabBarController.selectedIndex = 1 49 | } 50 | } 51 | 52 | // MARK: - UISplitViewControllerDelegate 53 | 54 | func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool { 55 | if let primaryTab = primaryViewController as? UITabBarController, primaryTab.selectedViewController is UINavigationController { 56 | return true 57 | } 58 | return false 59 | } 60 | 61 | func splitViewController(_ splitViewController: UISplitViewController, separateSecondaryFrom primaryViewController: UIViewController) -> UIViewController? { 62 | if let tabBarController = primaryViewController as? UITabBarController, let navigationController = tabBarController.selectedViewController as? UINavigationController, navigationController.children.count > 1 { 63 | guard let poppedControllers = navigationController.popToRootViewController(animated: false) else { 64 | return nil 65 | } 66 | let childNavigationController = UINavigationController() 67 | childNavigationController.viewControllers = poppedControllers 68 | return childNavigationController 69 | } 70 | return nil 71 | } 72 | 73 | func splitViewController(_ splitViewController: UISplitViewController, 74 | showDetail detailVc: UIViewController, 75 | sender: Any?) -> Bool { 76 | if splitViewController.isCollapsed, let master = splitViewController.viewControllers[0] as? UITabBarController, let masterNavigation = master.selectedViewController as? UINavigationController { 77 | masterNavigation.show(detailVc, sender: self) 78 | return true 79 | } else if let masterNavigation = splitViewController.viewControllers[1] as? UINavigationController { 80 | masterNavigation.setViewControllers([detailVc], animated: true) 81 | return true 82 | } 83 | return false 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Launch Screen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/MeetupCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MeetupCell.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Matthijs Hollemans on 25-05-15. 6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | private let dateFormatter = DateFormatter() 13 | 14 | class MeetupCell: UITableViewCell { 15 | 16 | @IBOutlet weak var titleLabel: UILabel! 17 | @IBOutlet weak var timeLabel: UILabel! 18 | @IBOutlet weak var logoImageView: UIImageView! 19 | @IBOutlet weak var dateContainer: UIView! 20 | @IBOutlet weak var monthBackgroundView: UIView! 21 | @IBOutlet weak var dayBackgroundView: UIView! 22 | @IBOutlet weak var dayLabel: UILabel! 23 | @IBOutlet weak var monthLabel: UILabel! 24 | @IBOutlet weak var rsvpLabel: UILabel! 25 | 26 | required init?(coder aDecoder: NSCoder) { 27 | super.init(coder: aDecoder) 28 | } 29 | 30 | override func awakeFromNib() { 31 | super.awakeFromNib() 32 | } 33 | 34 | override func prepareForReuse() { 35 | super.prepareForReuse() 36 | 37 | titleLabel.text = "" 38 | timeLabel.text = "" 39 | dayLabel.text = "" 40 | monthLabel.text = "" 41 | } 42 | 43 | func configureCellForMeetup(_ meetup: Meetup) { 44 | titleLabel.text = meetup.name 45 | 46 | if let date = meetup.time { 47 | dateFormatter.dateStyle = .none 48 | dateFormatter.timeStyle = .short 49 | let timeText = dateFormatter.string(from: date as Date) 50 | if meetup.latitude == 0.0 && meetup.longitude == 0.0 { 51 | timeLabel.text = timeText 52 | } else { 53 | timeLabel.text = String(format: "%@ %@", meetup.location ?? NSLocalizedString("Location unknown"), timeText) 54 | } 55 | 56 | dateFormatter.dateFormat = "dd" 57 | dayLabel.text = dateFormatter.string(from: date as Date) 58 | 59 | dateFormatter.dateFormat = "MMM" 60 | monthLabel.text = dateFormatter.string(from: date as Date).uppercased() 61 | dateFormatter.dateFormat = "MMMM" 62 | monthLabel.accessibilityLabel = dateFormatter.string(from: date as Date) 63 | 64 | if meetup.isToday || meetup.isUpcoming { 65 | dayLabel.textColor = UIColor(named: "DayLabelColor") 66 | monthBackgroundView.backgroundColor = meetup.isToday ? UIColor(named: "DayBackgroundViewColorToday") : UIColor(named: "DayBackgroundViewColor") 67 | 68 | if meetup.yesRsvpCount > 0 { 69 | 70 | rsvpLabel.text = "\(meetup.yesRsvpCount) " + NSLocalizedString("CocoaHeads going") 71 | 72 | if meetup.rsvpLimit > 0 { 73 | let text = rsvpLabel.text! + "\n\(meetup.rsvpLimit - meetup.yesRsvpCount) " 74 | rsvpLabel.text = text + NSLocalizedString("seats available") 75 | } 76 | } else { 77 | rsvpLabel.text = "" 78 | } 79 | } else { 80 | dayLabel.textColor = UIColor(named: "DayLabelColorHistory") 81 | monthBackgroundView.backgroundColor = UIColor(named: "DayBackgroundViewColorHistory") 82 | 83 | rsvpLabel.text = "\(meetup.yesRsvpCount) \(NSLocalizedString("CocoaHeads had a blast"))" 84 | } 85 | } 86 | 87 | self.logoImageView.image = meetup.smallLogoImage 88 | } 89 | 90 | override func setHighlighted(_ highlighted: Bool, animated: Bool) { 91 | super.setHighlighted(highlighted, animated: animated) 92 | 93 | if highlighted { 94 | monthBackgroundView.backgroundColor = .gray 95 | dayBackgroundView.backgroundColor = UIColor(white: 1, alpha: 0.4) 96 | } 97 | } 98 | 99 | override func setSelected(_ selected: Bool, animated: Bool) { 100 | super.setSelected(selected, animated: animated) 101 | 102 | if selected { 103 | monthBackgroundView.backgroundColor = .gray 104 | dayBackgroundView.backgroundColor = UIColor(white: 1, alpha: 0.4) 105 | } 106 | } 107 | } 108 | 109 | extension MeetupCell: Identifiable {} 110 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL.xcodeproj/xcshareddata/xcschemes/generalNotification.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 69 | 71 | 77 | 78 | 79 | 80 | 87 | 89 | 95 | 96 | 97 | 98 | 100 | 101 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | CocoaHeads 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(CURRENT_PROJECT_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | auto-generated 25 | ITSAppUsesNonExemptEncryption 26 | 27 | LSApplicationQueriesSchemes 28 | 29 | meetup 30 | 31 | LSRequiresIPhoneOS 32 | 33 | NSAppTransportSecurity 34 | 35 | NSExceptionDomains 36 | 37 | cocoaheads.nl 38 | 39 | NSExceptionRequiresForwardSecrecy 40 | 41 | 42 | files.parsetfss.com 43 | 44 | NSIncludesSubdomains 45 | 46 | 47 | jobs.cocoaheads.nl 48 | 49 | NSTemporaryExceptionAllowsInsecureHTTPLoads 50 | 51 | 52 | mzstatic.com 53 | 54 | NSIncludesSubdomains 55 | 56 | NSTemporaryExceptionAllowsInsecureHTTPLoads 57 | 58 | 59 | 60 | 61 | NSLocationAlwaysUsageDescription 62 | Your location is used to show you which companies are nearby. 63 | NSLocationWhenInUseUsageDescription 64 | Your location is used to show you which companies are nearby. 65 | UIApplicationShortcutItems 66 | 67 | 68 | UIApplicationShortcutItemIconType 69 | UIApplicationShortcutIconTypeSearch 70 | UIApplicationShortcutItemTitle 71 | Show companies 72 | UIApplicationShortcutItemType 73 | nl.cocoaheads.CocoaHeadsNL.companies 74 | 75 | 76 | UIApplicationShortcutItemIconType 77 | UIApplicationShortcutIconTypeSearch 78 | UIApplicationShortcutItemTitle 79 | Show jobs 80 | UIApplicationShortcutItemType 81 | nl.cocoaheads.CocoaHeadsNL.job 82 | 83 | 84 | UIApplicationShortcutItemIconType 85 | UIApplicationShortcutIconTypeSearch 86 | UIApplicationShortcutItemTitle 87 | Show meetups 88 | UIApplicationShortcutItemType 89 | nl.cocoaheads.CocoaHeadsNL.meetup 90 | 91 | 92 | UIBackgroundModes 93 | 94 | fetch 95 | remote-notification 96 | 97 | UILaunchStoryboardName 98 | Launch Screen 99 | UIMainStoryboardFile 100 | Main 101 | UIRequiredDeviceCapabilities 102 | 103 | armv7 104 | 105 | UIStatusBarTintParameters 106 | 107 | UINavigationBar 108 | 109 | Style 110 | UIBarStyleDefault 111 | Translucent 112 | 113 | 114 | 115 | UISupportedInterfaceOrientations 116 | 117 | UIInterfaceOrientationPortrait 118 | UIInterfaceOrientationLandscapeLeft 119 | UIInterfaceOrientationLandscapeRight 120 | UIInterfaceOrientationPortraitUpsideDown 121 | 122 | UISupportedInterfaceOrientations~ipad 123 | 124 | UIInterfaceOrientationPortrait 125 | UIInterfaceOrientationLandscapeLeft 126 | UIInterfaceOrientationLandscapeRight 127 | UIInterfaceOrientationPortraitUpsideDown 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL.xcodeproj/xcshareddata/xcschemes/ItemServiceExtension.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 69 | 70 | 76 | 77 | 78 | 79 | 86 | 89 | 90 | 91 | 97 | 98 | 99 | 100 | 102 | 103 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL.xcodeproj/xcshareddata/xcschemes/itemNotification.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 69 | 73 | 74 | 75 | 81 | 82 | 83 | 84 | 91 | 93 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "AppIcon-58.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "AppIcon-87.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "AppIcon-80.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "AppIcon-120.png", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "size" : "60x60", 39 | "idiom" : "iphone", 40 | "filename" : "AppIcon-120-1.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "AppIcon-180.png", 47 | "scale" : "3x" 48 | }, 49 | { 50 | "idiom" : "ipad", 51 | "size" : "20x20", 52 | "scale" : "1x" 53 | }, 54 | { 55 | "idiom" : "ipad", 56 | "size" : "20x20", 57 | "scale" : "2x" 58 | }, 59 | { 60 | "size" : "29x29", 61 | "idiom" : "ipad", 62 | "filename" : "AppIcon-29.png", 63 | "scale" : "1x" 64 | }, 65 | { 66 | "size" : "29x29", 67 | "idiom" : "ipad", 68 | "filename" : "AppIcon-58-1.png", 69 | "scale" : "2x" 70 | }, 71 | { 72 | "size" : "40x40", 73 | "idiom" : "ipad", 74 | "filename" : "AppIcon-40.png", 75 | "scale" : "1x" 76 | }, 77 | { 78 | "size" : "40x40", 79 | "idiom" : "ipad", 80 | "filename" : "AppIcon-80-1.png", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "size" : "76x76", 85 | "idiom" : "ipad", 86 | "filename" : "AppIcon-76.png", 87 | "scale" : "1x" 88 | }, 89 | { 90 | "size" : "76x76", 91 | "idiom" : "ipad", 92 | "filename" : "AppIcon-152.png", 93 | "scale" : "2x" 94 | }, 95 | { 96 | "size" : "83.5x83.5", 97 | "idiom" : "ipad", 98 | "filename" : "AppIcon-167.png", 99 | "scale" : "2x" 100 | }, 101 | { 102 | "size" : "1024x1024", 103 | "idiom" : "ios-marketing", 104 | "filename" : "iTunesArtwork@2x.png", 105 | "scale" : "1x" 106 | }, 107 | { 108 | "size" : "24x24", 109 | "idiom" : "watch", 110 | "filename" : "AppIcon-48.png", 111 | "scale" : "2x", 112 | "role" : "notificationCenter", 113 | "subtype" : "38mm" 114 | }, 115 | { 116 | "size" : "27.5x27.5", 117 | "idiom" : "watch", 118 | "filename" : "AppIcon-55.png", 119 | "scale" : "2x", 120 | "role" : "notificationCenter", 121 | "subtype" : "42mm" 122 | }, 123 | { 124 | "size" : "29x29", 125 | "idiom" : "watch", 126 | "filename" : "AppIcon-58-2.png", 127 | "role" : "companionSettings", 128 | "scale" : "2x" 129 | }, 130 | { 131 | "size" : "29x29", 132 | "idiom" : "watch", 133 | "filename" : "AppIcon-87-1.png", 134 | "role" : "companionSettings", 135 | "scale" : "3x" 136 | }, 137 | { 138 | "size" : "40x40", 139 | "idiom" : "watch", 140 | "filename" : "AppIcon-80-2.png", 141 | "scale" : "2x", 142 | "role" : "appLauncher", 143 | "subtype" : "38mm" 144 | }, 145 | { 146 | "size" : "44x44", 147 | "idiom" : "watch", 148 | "scale" : "2x", 149 | "role" : "appLauncher", 150 | "subtype" : "40mm" 151 | }, 152 | { 153 | "size" : "50x50", 154 | "idiom" : "watch", 155 | "scale" : "2x", 156 | "role" : "appLauncher", 157 | "subtype" : "44mm" 158 | }, 159 | { 160 | "size" : "86x86", 161 | "idiom" : "watch", 162 | "filename" : "AppIcon-176.png", 163 | "scale" : "2x", 164 | "role" : "quickLook", 165 | "subtype" : "38mm" 166 | }, 167 | { 168 | "size" : "98x98", 169 | "idiom" : "watch", 170 | "filename" : "AppIcon-196.png", 171 | "scale" : "2x", 172 | "role" : "quickLook", 173 | "subtype" : "42mm" 174 | }, 175 | { 176 | "size" : "108x108", 177 | "idiom" : "watch", 178 | "scale" : "2x", 179 | "role" : "quickLook", 180 | "subtype" : "44mm" 181 | }, 182 | { 183 | "idiom" : "watch-marketing", 184 | "size" : "1024x1024", 185 | "scale" : "1x" 186 | } 187 | ], 188 | "info" : { 189 | "version" : 1, 190 | "author" : "xcode" 191 | } 192 | } -------------------------------------------------------------------------------- /CocoaHeadsNL/CloudKitSync/jobs/xmlreader.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013 Sam Decrock 3 | 4 | MIT License 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | var sax = require('./sax'); 26 | 27 | exports.read = function(xmlstring, callback){ 28 | var saxparser = sax.parser(true); 29 | var rootobject = {}; 30 | var object = rootobject; 31 | 32 | saxparser.onerror = function (err) { 33 | // an error happened. 34 | 35 | return callback(err); 36 | }; 37 | 38 | saxparser.onopentag = function (node) { 39 | // opened a tag. node has "name" and "attributes" 40 | 41 | // create a new object and fill it with a function that returns the attributes: 42 | var newobject = { 43 | attributes: function(name){ 44 | return node.attributes; 45 | } 46 | }; 47 | 48 | // add the parent() function so that we can use it later: 49 | addParentFunction(newobject, object); 50 | 51 | // add the functions count(), at() and each() to access the nodes as if they were multiple nodes of the same name: 52 | addCountFunction(newobject); 53 | addAtFunction(newobject); 54 | addEachFunction(newobject); 55 | 56 | // check if a node with that name already exists 57 | if(object[node.name]){ 58 | // we're dealing with objects of the same name, let's wrap them in an array 59 | 60 | // check if there's already an array: 61 | if(!object[node.name].array){ 62 | // no array, create one and at itself + newobject: 63 | var firstobject = object[node.name]; 64 | object[node.name] = {}; 65 | object[node.name].array = new Array(firstobject, newobject); 66 | }else{ 67 | // alreay an array, just add the newobject to it: 68 | object[node.name].array.push(newobject); 69 | } 70 | 71 | // add 3 functions to work with that array: 72 | addCountFunction(object[node.name]); 73 | addAtFunction(object[node.name]); 74 | addEachFunction(object[node.name]); 75 | }else{ 76 | // add the functions count() and at() to access the nodes from the array: 77 | object[node.name] = newobject; 78 | } 79 | 80 | // set the current object to the newobject: 81 | object = newobject; 82 | }; 83 | 84 | saxparser.oncdata = function(cdata){ 85 | // add the function text() to the object to return the cdata value: 86 | object.text = function(){ 87 | return cdata; 88 | } 89 | }; 90 | 91 | saxparser.ontext = function (text) { 92 | // add the function text() to the object to return the text value: 93 | !object.text ? object.text = function(){ 94 | return text; 95 | } : null; 96 | }; 97 | 98 | saxparser.onclosetag = function (node) { 99 | // set the object back to its parent: 100 | object = object.parent(); 101 | } 102 | 103 | saxparser.onend = function () { 104 | return callback(null, rootobject); 105 | }; 106 | 107 | // Functions that add functions like count(), at() and parent() 108 | // We need closures for this: 109 | function addCountFunction(object){ 110 | if(object.array){ 111 | object.count = function(){ 112 | return object.array.length; 113 | } 114 | }else{ 115 | object.count = function(){ 116 | return 1; 117 | } 118 | } 119 | } 120 | 121 | function addAtFunction(object){ 122 | if(object.array){ 123 | object.at = function(index){ 124 | return object.array[index]; 125 | } 126 | }else{ 127 | object.at = function(index){ 128 | return object; 129 | } 130 | } 131 | } 132 | 133 | function addEachFunction(object){ 134 | if(object.array){ 135 | object.each = function(callback){ 136 | for(var i in object.array){ 137 | callback(i, object.array[i]); 138 | } 139 | return; 140 | } 141 | }else{ 142 | object.each = function(callback){ 143 | return callback(0, object); 144 | } 145 | } 146 | } 147 | 148 | 149 | 150 | function addParentFunction(object, parent){ 151 | object.parent = function(){ 152 | return parent; 153 | } 154 | } 155 | 156 | // pass the xml string to the awesome sax parser: 157 | saxparser.write(xmlstring).close(); 158 | } -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/NSManagedObjectContext+SaveHelpers.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | public enum SuccessResult { 4 | /// A success case 5 | case success 6 | /// A failure case with associated ErrorType instance 7 | case failure(Swift.Error) 8 | } 9 | 10 | public typealias SaveResult = SuccessResult 11 | public typealias CoreDataStackSaveCompletion = (SaveResult) -> Void 12 | 13 | /** 14 | Convenience extension to `NSManagedObjectContext` that ensures that saves to contexts of type 15 | `MainQueueConcurrencyType` and `PrivateQueueConcurrencyType` are dispatched on the correct GCD queue. 16 | */ 17 | public extension NSManagedObjectContext { 18 | 19 | /** 20 | Convenience method to synchronously save the `NSManagedObjectContext` if changes are present. 21 | Method also ensures that the save is executed on the correct queue when using Main/Private queue concurrency types. 22 | - throws: Errors produced by the `save()` function on the `NSManagedObjectContext` 23 | */ 24 | func saveContextAndWait() throws { 25 | switch concurrencyType { 26 | case .confinementConcurrencyType: 27 | try sharedSaveFlow() 28 | case .mainQueueConcurrencyType, 29 | .privateQueueConcurrencyType: 30 | try performAndWaitOrThrow(sharedSaveFlow) 31 | @unknown default: 32 | fatalError() 33 | } 34 | } 35 | 36 | /** 37 | Convenience method to asynchronously save the `NSManagedObjectContext` if changes are present. 38 | Method also ensures that the save is executed on the correct queue when using Main/Private queue concurrency types. 39 | - parameter completion: Completion closure with a `SaveResult` to be executed upon the completion of the save operation. 40 | */ 41 | func saveContext(_ completion: CoreDataStackSaveCompletion? = nil) { 42 | func saveFlow() { 43 | do { 44 | try sharedSaveFlow() 45 | completion?(.success) 46 | } catch let saveError { 47 | completion?(.failure(saveError)) 48 | } 49 | } 50 | 51 | switch concurrencyType { 52 | case .confinementConcurrencyType: 53 | saveFlow() 54 | case .privateQueueConcurrencyType, 55 | .mainQueueConcurrencyType: 56 | perform(saveFlow) 57 | @unknown default: 58 | fatalError() 59 | } 60 | } 61 | 62 | /** 63 | Convenience method to synchronously save the `NSManagedObjectContext` if changes are present. 64 | If any parent contexts are found, they too will be saved synchronously. 65 | Method also ensures that the save is executed on the correct queue when using Main/Private queue concurrency types. 66 | - throws: Errors produced by the `save()` function on the `NSManagedObjectContext` 67 | */ 68 | func saveContextToStoreAndWait() throws { 69 | func saveFlow() throws { 70 | try sharedSaveFlow() 71 | if let parentContext = parent { 72 | try parentContext.saveContextToStoreAndWait() 73 | } 74 | } 75 | 76 | switch concurrencyType { 77 | case .confinementConcurrencyType: 78 | try saveFlow() 79 | case .mainQueueConcurrencyType, 80 | .privateQueueConcurrencyType: 81 | try performAndWaitOrThrow(saveFlow) 82 | @unknown default: 83 | fatalError() 84 | } 85 | } 86 | 87 | /** 88 | Convenience method to asynchronously save the `NSManagedObjectContext` if changes are present. 89 | If any parent contexts are found, they too will be saved asynchronously. 90 | Method also ensures that the save is executed on the correct queue when using Main/Private queue concurrency types. 91 | - parameter completion: Completion closure with a `SaveResult` to be executed 92 | either upon the completion of the top most context's save operation or the first encountered save error. 93 | */ 94 | func saveContextToStore(_ completion: CoreDataStackSaveCompletion? = nil) { 95 | func saveFlow() { 96 | do { 97 | try sharedSaveFlow() 98 | if let parentContext = parent { 99 | parentContext.saveContextToStore(completion) 100 | } else { 101 | completion?(.success) 102 | } 103 | } catch let saveError { 104 | completion?(.failure(saveError)) 105 | } 106 | } 107 | 108 | switch concurrencyType { 109 | case .confinementConcurrencyType: 110 | saveFlow() 111 | case .privateQueueConcurrencyType, 112 | .mainQueueConcurrencyType: 113 | perform(saveFlow) 114 | @unknown default: 115 | fatalError() 116 | } 117 | } 118 | 119 | private func sharedSaveFlow() throws { 120 | guard hasChanges else { 121 | return 122 | } 123 | 124 | try save() 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL.xcodeproj/xcshareddata/xcschemes/CocoaHeadsNL.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 86 | 88 | 94 | 95 | 96 | 97 | 103 | 105 | 111 | 112 | 113 | 114 | 116 | 117 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/Model.xcdatamodeld/Model.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/NSManagedObject+FetchHelpers.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | import CoreData 4 | 5 | /** 6 | Extends `NSFetchRequestResult` with 7 | methods that make fetching, inserting, deleting, and change management easier. 8 | */ 9 | @available(iOS, introduced: 10.0) 10 | @available(tvOS, introduced: 10.0) 11 | @available(OSX, introduced: 10.12) 12 | extension NSFetchRequestResult where Self: NSManagedObject { 13 | /** 14 | Creates a new fetch request for the `NSManagedObject` entity. 15 | - parameter context: `NSManagedObjectContext` to create the object within. 16 | - returns: `NSFetchRequest`: The new fetch request. 17 | */ 18 | public static func fetchRequestForEntity(inContext context: NSManagedObjectContext) -> NSFetchRequest { 19 | let fetchRequest = NSFetchRequest() 20 | fetchRequest.entity = entity() 21 | return fetchRequest 22 | } 23 | 24 | /** 25 | Fetches the first Entity that matches the optional predicate within the specified `NSManagedObjectContext`. 26 | - parameter context: `NSManagedObjectContext` to find the entities within. 27 | - parameter predicate: An optional `NSPredicate` for filtering 28 | - throws: Any error produced from `executeFetchRequest` 29 | - returns: `Self?`: The first entity that matches the optional predicate or `nil`. 30 | */ 31 | public static func findFirstInContext(_ context: NSManagedObjectContext, predicate: NSPredicate? = nil) throws -> Self? { 32 | let fetchRequest = fetchRequestForEntity(inContext: context) 33 | fetchRequest.predicate = predicate 34 | fetchRequest.fetchLimit = 1 35 | fetchRequest.returnsObjectsAsFaults = false 36 | fetchRequest.fetchBatchSize = 1 37 | return try context.fetch(fetchRequest).first 38 | } 39 | 40 | /** 41 | Fetches all Entities within the specified `NSManagedObjectContext`. 42 | - parameter context: `NSManagedObjectContext` to find the entities within. 43 | - parameter sortDescriptors: Optional array of `NSSortDescriptors` to apply to the fetch 44 | - parameter predicate: An optional `NSPredicate` for filtering 45 | - throws: Any error produced from `executeFetchRequest` 46 | - returns: `[Self]`: The array of matching entities. 47 | */ 48 | public static func allInContext(_ context: NSManagedObjectContext, predicate: NSPredicate? = nil, sortDescriptors: [NSSortDescriptor]? = nil) throws -> [Self] { 49 | let fetchRequest = fetchRequestForEntity(inContext: context) 50 | fetchRequest.sortDescriptors = sortDescriptors 51 | fetchRequest.predicate = predicate 52 | return try context.fetch(fetchRequest) 53 | } 54 | 55 | // MARK: - Counting Objects 56 | /** 57 | Returns count of Entities that matches the optional predicate within the specified `NSManagedObjectContext`. 58 | - parameter context: `NSManagedObjectContext` to count the entities within. 59 | - parameter predicate: An optional `NSPredicate` for filtering 60 | - throws: Any error produced from `countForFetchRequest` 61 | - returns: `Int`: Count of entities that matches the optional predicate. 62 | */ 63 | public static func countInContext(_ context: NSManagedObjectContext, predicate: NSPredicate? = nil) throws -> Int { 64 | let fetchReqeust = fetchRequestForEntity(inContext: context) 65 | fetchReqeust.includesSubentities = false 66 | fetchReqeust.predicate = predicate 67 | return try context.count(for: fetchReqeust) 68 | } 69 | 70 | // MARK: - Removing Objects 71 | /** 72 | Removes all entities from within the specified `NSManagedObjectContext`. 73 | - parameter context: `NSManagedObjectContext` to remove the entities from. 74 | - throws: Any error produced from `executeFetchRequest` 75 | */ 76 | public static func removeAllInContext(_ context: NSManagedObjectContext) throws { 77 | let fetchRequest = fetchRequestForEntity(inContext: context) 78 | try removeAllObjectsReturnedByRequest(fetchRequest, inContext: context) 79 | } 80 | 81 | /** 82 | Removes all entities from within the specified `NSManagedObjectContext` excluding a supplied array of entities. 83 | - parameter context: The `NSManagedObjectContext` to remove the Entities from. 84 | - parameter except: An Array of `NSManagedObjects` belonging to the `NSManagedObjectContext` to exclude from deletion. 85 | - throws: Any error produced from `executeFetchRequest` 86 | */ 87 | public static func removeAllInContext(_ context: NSManagedObjectContext, except toKeep: [Self]) throws { 88 | let fetchRequest = fetchRequestForEntity(inContext: context) 89 | fetchRequest.predicate = NSPredicate(format: "NOT (self IN %@)", toKeep) 90 | try removeAllObjectsReturnedByRequest(fetchRequest, inContext: context) 91 | } 92 | 93 | // MARK: Private Funcs 94 | private static func removeAllObjectsReturnedByRequest(_ fetchRequest: NSFetchRequest, inContext context: NSManagedObjectContext) throws { 95 | // A batch delete would be more efficient here on iOS 9 and up 96 | // however it complicates things since the request requires a context with 97 | // an NSPersistentStoreCoordinator directly connected. (MOC cannot be a child of another MOC) 98 | fetchRequest.includesPropertyValues = false 99 | fetchRequest.includesSubentities = false 100 | try context.fetch(fetchRequest).lazy.forEach(context.delete(_:)) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /CocoaHeadsNL/generalNotification/Base.lproj/MainInterface.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Bart Hoffman on 07/03/15. 6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreSpotlight 11 | import CloudKit 12 | 13 | import CoreData 14 | 15 | import UserNotifications 16 | 17 | let searchNotificationName = "CocoaHeadsNLSpotLightSearchOccured" 18 | let searchPasteboardName = "CocoaHeadsNL-searchInfo-pasteboard" 19 | 20 | @UIApplicationMain 21 | class AppDelegate: UIResponder, UIApplicationDelegate { 22 | var window: UIWindow? 23 | 24 | func applicationDidBecomeActive(_ application: UIApplication) { 25 | if let pasteboard = UIPasteboard(name: UIPasteboard.Name(rawValue: "searchPasteboardName"), create: false) { 26 | pasteboard.string = "" 27 | } 28 | 29 | UIApplication.shared.applicationIconBadgeNumber = 0 30 | } 31 | 32 | func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { 33 | // TODO: Do we need this? 34 | } 35 | 36 | func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { 37 | if error._code == 3010 { 38 | print("Push Notifications are not supported in the simulator") 39 | } else { 40 | print("application didFailToRegisterForRemoteNotificationsWithError: %@", error) 41 | } 42 | } 43 | 44 | func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) { 45 | 46 | guard let userInfo = userInfo as? [String: NSObject], let cloudKitNotification = CKNotification(fromRemoteNotificationDictionary: userInfo) else { return } 47 | if cloudKitNotification.notificationType == .query, 48 | let queryNotification = cloudKitNotification as? CKQueryNotification { 49 | //TODO: handle the different notifications to show the correct items 50 | let recordID = queryNotification.recordID 51 | print(recordID as Any) 52 | //... 53 | self.presentMeetupsViewController() 54 | } 55 | 56 | } 57 | 58 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 59 | 60 | _ = CoreDataStack.shared.persistentContainer 61 | 62 | UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in 63 | if !granted { 64 | print("Notifications not granted") 65 | } 66 | 67 | if let error = error { 68 | print("Error occured when requesting notification authorization. \(error)") 69 | } 70 | } 71 | UNUserNotificationCenter.current().delegate = self 72 | 73 | application.registerForRemoteNotifications() 74 | 75 | return true 76 | } 77 | 78 | func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { 79 | if userActivity.activityType == CSSearchableItemActionType { 80 | guard let uniqueIdentifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String else { 81 | return false 82 | } 83 | let components = uniqueIdentifier.components(separatedBy: ":") 84 | let type = components[0] 85 | let objectId = components[1] 86 | if type == "job" || type == "meetup" { 87 | //post uniqueIdentifier string to paste board 88 | let pasteboard = UIPasteboard(name: UIPasteboard.Name(rawValue: "searchPasteboardName"), create: true) 89 | pasteboard?.string = uniqueIdentifier 90 | 91 | //open tab, select based on uniqueId 92 | NotificationCenter.default.post(name: Notification.Name(rawValue: searchNotificationName), object: self, userInfo: ["type": type, "objectId": objectId]) 93 | return true 94 | } 95 | } 96 | 97 | return false 98 | } 99 | 100 | func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { 101 | handleShortCutItem(shortcutItem) 102 | completionHandler(true) 103 | } 104 | 105 | func handleShortCutItem(_ shortCutItem: UIApplicationShortcutItem) { 106 | 107 | switch shortCutItem.type { 108 | case "nl.cocoaheads.CocoaHeadsNL.meetup" : 109 | presentMeetupsViewController() 110 | case "nl.cocoaheads.CocoaHeadsNL.job" : 111 | presentJobsViewController() 112 | case "nl.cocoaheads.CocoaHeadsNL.companies" : 113 | presentCompaniesViewController() 114 | default: break 115 | } 116 | } 117 | 118 | func presentMeetupsViewController() { 119 | if let splitViewController = self.window?.rootViewController as? SplitViewController { 120 | if let tabBar = splitViewController.viewControllers[0] as? UITabBarController { 121 | tabBar.selectedIndex = 0 122 | } 123 | } 124 | } 125 | 126 | func presentJobsViewController() { 127 | if let splitViewController = self.window?.rootViewController as? SplitViewController { 128 | if let tabBar = splitViewController.viewControllers[0] as? UITabBarController { 129 | tabBar.selectedIndex = 1 130 | } 131 | } 132 | } 133 | 134 | func presentCompaniesViewController() { 135 | if let splitViewController = self.window?.rootViewController as? SplitViewController { 136 | if let tabBar = splitViewController.viewControllers[0] as? UITabBarController { 137 | tabBar.selectedIndex = 2 138 | } 139 | } 140 | } 141 | } 142 | 143 | extension AppDelegate: UNUserNotificationCenterDelegate { 144 | 145 | func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { 146 | completionHandler() 147 | } 148 | func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { 149 | completionHandler([.alert, .sound]) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/DetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailViewController.swift 3 | // CocoaHeadsNL 4 | // 5 | // Created by Bart Hoffman on 14/03/15. 6 | // Copyright (c) 2016 Stichting CocoaheadsNL. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import StoreKit 11 | 12 | extension UIResponder { 13 | @objc func reloadCell(_ cell: UITableViewCell) { 14 | self.next?.reloadCell(cell) 15 | } 16 | } 17 | 18 | class DetailViewController: UITableViewController, SKStoreProductViewControllerDelegate { 19 | var dataSource: DetailDataSource! 20 | weak var presentingVC: UIViewController? 21 | fileprivate var activityViewController: UIActivityViewController? 22 | 23 | required init?(coder aDecoder: NSCoder) { 24 | super.init(coder: aDecoder) 25 | } 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | 30 | tableView.tableFooterView = UIView() 31 | 32 | //Can be used to hide masterViewController and increase size of detailView if wanted 33 | self.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem 34 | self.navigationItem.leftItemsSupplementBackButton = true 35 | 36 | //For some reason this triggers correct resizing behavior when rotating views. 37 | self.tableView.estimatedRowHeight = 100 38 | 39 | if let dataSource = dataSource { 40 | dataSource.tableView = self.tableView 41 | self.tableView.dataSource = dataSource 42 | self.tableView.delegate = dataSource 43 | self.navigationItem.title = dataSource.title 44 | } 45 | 46 | if let data = dataSource as? CompanyDataSource { 47 | data.presenter = self 48 | } 49 | 50 | self.tableView.reloadData() 51 | } 52 | 53 | override func viewDidAppear(_ animated: Bool) { 54 | super.viewDidAppear(animated) 55 | 56 | var url: URL? 57 | var title: String? 58 | var activityType: String? 59 | 60 | if let companyDataSource = dataSource as? CompanyDataSource { 61 | if let urlString = companyDataSource.company.website, let titleString = companyDataSource.company.name { 62 | title = titleString 63 | url = URL(string: urlString) 64 | activityType = "nl.cocoaheads.app.company" 65 | } 66 | } else if let jobsDataSource = dataSource as? JobDataSource { 67 | title = jobsDataSource.job.title 68 | url = URL(string: jobsDataSource.job.link!) 69 | activityType = "nl.cocoaheads.app.job" 70 | } else if let meetupDataSource = dataSource as? MeetupDataSource, let urlString = meetupDataSource.meetup.meetupUrl { 71 | title = meetupDataSource.meetup.name 72 | url = URL(string: urlString) 73 | activityType = "nl.cocoaheads.app.meetup" 74 | } 75 | 76 | if let title = title, let url = url, let activityType = activityType { 77 | let activity = NSUserActivity(activityType: activityType) 78 | activity.title = title 79 | 80 | guard var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { 81 | return 82 | } 83 | 84 | if urlComponents.scheme == nil { 85 | urlComponents.scheme = "http" 86 | } 87 | 88 | guard let checkedUrl = urlComponents.url, urlComponents.scheme == "http" || urlComponents.scheme == "https" else { 89 | return 90 | } 91 | 92 | activity.webpageURL = checkedUrl 93 | activity.becomeCurrent() 94 | self.userActivity = activity 95 | } 96 | } 97 | 98 | override func viewDidDisappear(_ animated: Bool) { 99 | super.viewDidDisappear(animated) 100 | 101 | self.userActivity?.resignCurrent() 102 | } 103 | 104 | override func reloadCell(_ cell: UITableViewCell) { 105 | tableView.beginUpdates() 106 | tableView.endUpdates() 107 | } 108 | 109 | func showStoreView(_ parameters: [String: AnyObject], indexPath: IndexPath) { 110 | 111 | let storeViewController = SKStoreProductViewController() 112 | storeViewController.delegate = self 113 | 114 | storeViewController.loadProduct(withParameters: parameters, 115 | completionBlock: {result, _ in 116 | if result { 117 | self.present(storeViewController, 118 | animated: true, completion: nil) 119 | self.tableView.deselectRow(at: indexPath, animated: true) 120 | } 121 | }) 122 | } 123 | 124 | func productViewControllerDidFinish(_ viewController: SKStoreProductViewController) { 125 | self.dismiss(animated: true, completion: nil) 126 | } 127 | 128 | override var previewActionItems: [UIPreviewActionItem] { 129 | let shareAction = UIPreviewAction(title: NSLocalizedString("Share"), style: .default) { _, _ in 130 | 131 | if let meetup = self.dataSource.object as? Meetup, let meetupId = meetup.meetupId { 132 | let string: String = "http://www.meetup.com/CocoaHeadsNL/events/\(meetupId)/" 133 | let URL: Foundation.URL = Foundation.URL(string: string)! 134 | 135 | let acViewController = UIActivityViewController(activityItems: [string, URL], applicationActivities: nil) 136 | 137 | if let meetupVC = self.presentingVC { 138 | self.activityViewController = acViewController 139 | meetupVC.present(self.activityViewController!, animated: true, completion: nil) 140 | } 141 | } 142 | } 143 | 144 | let rsvpAction = UIPreviewAction(title: NSLocalizedString("RSVP"), style: .default) { _, _ in 145 | 146 | if let meetup = self.dataSource.object as? Meetup, let meetupId = meetup.meetupId { 147 | if let URL = URL(string: "http://www.meetup.com/CocoaHeadsNL/events/\(meetupId)/") { 148 | UIApplication.shared.open(URL, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: { _ in 149 | }) 150 | } 151 | } 152 | } 153 | 154 | return [rsvpAction, shareAction] 155 | } 156 | } 157 | 158 | // Helper function inserted by Swift 4.2 migrator. 159 | private func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] { 160 | return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value) }) 161 | } 162 | -------------------------------------------------------------------------------- /CocoaHeadsNL/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - CocoaHeadsNL 3 | 4 | excluded: 5 | - Pods 6 | - Frameworks 7 | - bin 8 | - Build-Phases 9 | - Products 10 | 11 | opt_in_rules: 12 | # 0️⃣ - Geen meerwaarde 13 | # 1️⃣ - Minimale meerwaarde 14 | # 2️⃣ - Gemiddelde meerwaarde 15 | # 3️⃣ - Hoge meerwaarde 16 | # 4️⃣ - No-brainers 17 | # 🚗 - supports autocorrect 18 | # ⚠️ - Number of warnings 19 | 20 | # Stylistic Rules 💅🏻 21 | # - let_var_whitespace # 1️⃣ ⚠️ 434, Zorg ervoor dat in variable en een functie niet tegen elkaar aanstaan (protocols) style 22 | - operator_usage_whitespace # 1️⃣ 🚗 dubbele spaties, missende spaties 23 | - closure_end_indentation # 1️⃣ 🚗, { } closures 24 | - closure_parameter_position # 1️⃣, { text within closures } 25 | # - collection_alignment # 1️⃣ ⚠️ 3, 3 regels code die niet netjes uitgelijnd zijn. 26 | # - conditional_returns_on_newline # 1️⃣ ⚠️ 2, # only for -if statements 27 | # - function_default_parameter_at_end # 1️⃣ ⚠️ 10, #in functies worden default parameters aan het einde gezet: func bla(foo: String, roo: Int = 200) 28 | # - multiline_arguments # 1️⃣ ⚠️ 49, Of alle argumenten van een functie in 1 regel of alle argumenten per regel en niet 3 argumenten op regel 1 en 4 argumenten op regel 2 29 | - multiline_parameters # 1️⃣, parameters of allemaal op 1 regel of per regel een parameter 30 | # - multiline_function_chains # 1️⃣ ⚠️ 129, chaining op meerdere regels, zorgt voor structuur 31 | # - multiline_parameters_brackets # 1️⃣ ⚠️ 10, 32 | - closure_spacing # 2️⃣ 🚗, spacing tussen clusures zorgt voor veel spatie irritatie 33 | 34 | # Hygienic Rules 🧻 35 | # - file_header # 0️⃣, ⚠️ 688 warnings 36 | # - closure_body_length # 1️⃣ ⚠️ 8, Teveel logica in een closure () -- Emiel 37 | - explicit_init # 1️⃣ 🚗, Compleet overbodig om init aan te roepen voor bijvoorbeeld: UIColor.init(hexcode: "5288f5") ipv UIColor(hexcode: "5288f5" 38 | # - toggle_bool # 1️⃣ ⚠️ 2, someBool.toggle() over someBool = !someBool. 39 | - unused_import # 1️⃣ 🚗 40 | - unneeded_parentheses_in_closure_argument # 3️⃣ 🚗, 41 | - redundant_type_annotation # 1️⃣ 🚗, let storyBoard: UIStoryboard = UIStoryboard("") 42 | # - empty_string # 4️⃣ ⚠️ 9 performance verbetering wanneer we aString.isEmpty gebruiken ipv aString == "" 43 | # - first_where # 4️⃣ ⚠️ 5 performance verbetering Prefer using .first(where:) over .filter { }.first in collections. 44 | - last_where # 4️⃣ performance, zelfde als first_where 45 | # - sorted_first_last # 4️⃣ ⚠️ 5 performance, gebruik maken van min/max functies van array's ipv sorteren en vervolgens .first/.last 46 | 47 | # Convention Rules 🤝 48 | - modifier_order # 1️⃣ 🚗 ⚠️ 17, style, 'class private func' moet worden 'private class func' 49 | # - no_grouping_extension # 1️⃣ ⚠️ 477, style, persoonelijk vind ik het niet logisch dat een class geextend wordt om daarin code te groeperen (alleen intereresant voor class extensies zonder dat ze ook een protocol inmplementeren) 50 | # - legacy_random # 2️⃣ ⚠️ 1, random is vernieuwd 51 | # - fatal_error_message # 2️⃣ ⚠️ 72, Helpt de andere developers, kleine moeite https://medium.com/@abhimuralidharan/what-are-fatal-errors-and-when-to-use-them-53ad1a66c6d6 52 | # - convenience_type # 3️⃣ ⚠️ 60, structs, classes en enums die verkeerd gebruikt worden (classes die geen struct zijn) En dit heeft invloed op de performance waarom: https://stackoverflow.com/a/24232845/2826164 53 | - prohibited_super_call 54 | - override_in_extension 55 | - unused_optional_binding 56 | - yoda_condition 57 | 58 | # Code Smells Rules 💩 59 | # - force_unwrapping # 3️⃣ ⚠️ 147, force unwrapping can cause crashes (is nu ook al een ongeschreven regel) 60 | # - implicitly_unwrapped_optional # 3️⃣ ⚠️ 96, avoid force unwrap 61 | - redundant_nil_coalescing # 2️⃣ 🚗 ⚠️ 3, var myVar: Int? = nil; myVar ?? nil 62 | # - contains_over_first_not_nil # 2️⃣ ⚠️ 4, performance verbetering 63 | # - pattern_matching_keywords # 3️⃣ ⚠️ 8, case let .foo(x, y): break ipv case .foo(↓let x, ↓let y): break 64 | 65 | # Bug Avoiding Rules 🐞 66 | - fallthrough # 2️⃣ switch falltrough zorgt voor onduidelijkheid, wordt gelukkig nergens gebruikt! Kan Aan! 67 | - no_fallthrough_only 68 | # - lower_acl_than_parent # 2️⃣ ⚠️ 292, onduidelijkheid: accesss van functies en variabelen die meer toegangkelijkheid bieden hebben dan de klasse 69 | # - no_extension_access_modifier # 2️⃣ ⚠️ 25, extensions horen niet het toegangsniveau aan te passen bijv: (private extension String) 70 | # - private_action # 3️⃣ ⚠️ 47, voorkomt om deze actions via een andere manier aan te roepen 71 | # - private_outlet # 3️⃣ ⚠️ 153, voorkomt om deze outlets via een andere klasse aangepast kunnen worden 72 | # - strong_iboutlet # 3️⃣ ⚠️ 341, kan crashes voorkomen als de view die de outlet bevat verdwijnt https://cocoacasts.com/should-outlets-be-weak-or-strong 73 | # - overridden_super_call # 3️⃣ ⚠️ 24, inpact onbekend, lijkt me goed om uit te zoeken 74 | # - file_name # 4️⃣ 26 ⚠️, met honderden bestanden is het belangrijk dat de "Bestandsnaam.swift" overeen komt met de klassenaam "Bestandennaam" 75 | - empty_count # 4️⃣, performance verbetering wanneer we count > 0 vervangen met isEmpty 76 | 77 | disabled_rules: 78 | - trailing_newline 79 | - trailing_whitespace 80 | 81 | # regels om aan te zetten: 82 | - large_tuple # Aanzetten, gebruik een struct of een class (max 3) 83 | - function_parameter_count # aanzetten, functies met meer dan 5 counts kunnen in de praktijk gewoon een struct of een class als parameter mee krijgen en dan daarnaar handelen 84 | 85 | 86 | type_name: 87 | min_length: 3 88 | max_length: 70 89 | 90 | # min_length op 3 met uitzondering voor Id 91 | identifier_name: 92 | min_length: 1 93 | max_length: 60 94 | allowed_symbols: "_" 95 | 96 | file_length: 97 | warning: 901 98 | error: 900 # files boven de 900 regels zijn error's 99 | 100 | conditional_returns_on_newline: 101 | if_only: true 102 | 103 | # Langste functie is 174 regels+ . het is belangrijk dat verantwoordelijkheid in functies wordt opgesplitst 104 | # 50 regels maximaal liever korter. Voor te lange functies is // swiftlint:disable:next function_body_length 105 | function_body_length: 106 | warning: 50 107 | error: 50 108 | 109 | large_tuple: 110 | warning: 3 111 | 112 | # Geen idee of hier enige waarde aan gehecht wordt, regellengte (emiel says: 'no') 113 | # Xcode, Settings, Text Editing, indention tab, 'Check linewrapping' om lengte van regel irritatie te voorkomen. 114 | line_length: 115 | warning: 301 116 | error: 300 117 | ignores_comments: true 118 | 119 | file_header: 120 | required_pattern: | 121 | \/\/ 122 | \/\/ .*?\.swift 123 | \/\/ EvenAppen 124 | \/\/ 125 | \/\/ Created by .*? on \d{1,2}\/\d{1,2}\/\d{2}\. 126 | \/\/ Copyright © \d{4} Achmea\. All rights reserved\. 127 | \/\/ 128 | 129 | reporter: "xcode" 130 | -------------------------------------------------------------------------------- /CocoaHeadsNL/CocoaHeadsNL/JobsCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | --------------------------------------------------------------------------------