Skip to content

Commit 3f84d3c

Browse files
v2.12-beta:
- New Feature! Read questions from a QR Code* * Create QR code using this JSON format in plain text: [ {"question": "Best desktop OS", "answers": ["macOS", "Windows", "MS-DOS 😎", "Linux c:"], "answer": 0}, {"question": "Best IDE", "answers": ["Visual Studio", "Xcode", "Netbeans", "Eclipse"], "answer": 1}, {"question": "Best smartphone OS", "answers": ["iOS", "Android", "BlackBerry OS", "Windows Phone c:"], "answer": 0} ] Try it here (select "Text"): http://www.qr-code-generator.com
1 parent 627f04d commit 3f84d3c

File tree

9 files changed

+264
-37
lines changed

9 files changed

+264
-37
lines changed

Questions.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
65CB4C4B1D29C3E300D2E1C1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 65CB4C491D29C3E300D2E1C1 /* Main.storyboard */; };
3232
65D1EAFF1D3E89E80003C8DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 65D1EAFE1D3E89E80003C8DA /* Assets.xcassets */; };
3333
65DD23E11E7422C800462512 /* LicensesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65DD23E01E7422C800462512 /* LicensesViewController.swift */; };
34+
65F4207F1E835A4F00BE90F0 /* QRScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F4207E1E835A4F00BE90F0 /* QRScannerViewController.swift */; };
3435
8E30C75E1D351E6000CD32B0 /* AVAudioPlayer+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E30C75D1D351E6000CD32B0 /* AVAudioPlayer+Extension.swift */; };
3536
8E7F0EFC1D351CEA0048F1A0 /* Quiz.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7F0EFB1D351CEA0048F1A0 /* Quiz.swift */; };
3637
/* End PBXBuildFile section */
@@ -94,6 +95,7 @@
9495
65CE0EEE1D2C3A5B00C163DB /* LICENCE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENCE; sourceTree = "<group>"; };
9596
65D1EAFE1D3E89E80003C8DA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
9697
65DD23E01E7422C800462512 /* LicensesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LicensesViewController.swift; sourceTree = "<group>"; };
98+
65F4207E1E835A4F00BE90F0 /* QRScannerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScannerViewController.swift; sourceTree = "<group>"; };
9799
8E30C75D1D351E6000CD32B0 /* AVAudioPlayer+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AVAudioPlayer+Extension.swift"; sourceTree = "<group>"; };
98100
8E7F0EFB1D351CEA0048F1A0 /* Quiz.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Quiz.swift; sourceTree = "<group>"; };
99101
/* End PBXFileReference section */
@@ -225,6 +227,7 @@
225227
65456AF31D2BC9620048965E /* TopicsViewController.swift */,
226228
654F9A391E775001002B6A82 /* QuizzesViewController.swift */,
227229
65C285D31D2AB6A4002F7DF8 /* QuestionsViewController.swift */,
230+
65F4207E1E835A4F00BE90F0 /* QRScannerViewController.swift */,
228231
);
229232
name = ViewControllers;
230233
sourceTree = "<group>";
@@ -432,6 +435,7 @@
432435
65C285D41D2AB6A4002F7DF8 /* QuestionsViewController.swift in Sources */,
433436
8E7F0EFC1D351CEA0048F1A0 /* Quiz.swift in Sources */,
434437
65456AF41D2BC9620048965E /* TopicsViewController.swift in Sources */,
438+
65F4207F1E835A4F00BE90F0 /* QRScannerViewController.swift in Sources */,
435439
65456FF81D77A07A004A13B6 /* UIColor+Extension.swift in Sources */,
436440
65456AF21D2BC01C0048965E /* String+Extension.swift in Sources */,
437441
65822A141E7D5F4C00A1FE61 /* UIAlertController+Extension.swift in Sources */,

Questions/Base.lproj/Main.storyboard

Lines changed: 80 additions & 23 deletions
Large diffs are not rendered by default.

Questions/Info.plist

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515
<key>CFBundlePackageType</key>
1616
<string>APPL</string>
1717
<key>CFBundleShortVersionString</key>
18-
<string>2.11.1-beta</string>
18+
<string>2.12-beta</string>
1919
<key>CFBundleSignature</key>
2020
<string>????</string>
2121
<key>CFBundleVersion</key>
2222
<string>1</string>
2323
<key>LSRequiresIPhoneOS</key>
2424
<true/>
25+
<key>NSCameraUsageDescription</key>
26+
<string>We need to access your camera for scanning QR code.</string>
2527
<key>UILaunchStoryboardName</key>
2628
<string>Main</string>
2729
<key>UIMainStoryboardFile</key>

Questions/LicensesViewController.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ class LicensesViewController: UIViewController {
1212

1313
licensesNavItem.title = "Licenses".localized
1414

15-
loadCurrentTheme()
1615
textView.attributedText = licensesAttributedText()
1716
textView.textAlignment = .center
1817
textView.textContainerInset = UIEdgeInsets(top: 30, left: 10, bottom: 30, right: 10)
18+
loadCurrentTheme()
1919

2020
setFrame()
2121

22-
NotificationCenter.default.addObserver(self, selector: #selector(self.setFrame),
22+
NotificationCenter.default.addObserver(self, selector: #selector(setFrame),
2323
name: NSNotification.Name.UIApplicationDidChangeStatusBarOrientation, object: nil)
2424

2525
NotificationCenter.default.addObserver(self, selector: #selector(loadCurrentTheme),

Questions/MainViewController.swift

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class MainViewController: UIViewController {
88
@IBOutlet weak var startButton: UIButton!
99
@IBOutlet weak var instructionsButton: UIButton!
1010
@IBOutlet weak var settingsButton: UIButton!
11+
@IBOutlet weak var readQRCodeButton: UIButton!
1112
@IBOutlet weak var scoreLabel: UILabel!
1213

1314
static var parallaxEffect = UIMotionEffectGroup()
@@ -32,7 +33,7 @@ class MainViewController: UIViewController {
3233
setFramesAndPosition()
3334

3435
// If user rotates screen, the buttons position and sizes are recalculated
35-
NotificationCenter.default.addObserver(self, selector: #selector(MainViewController.setFramesAndPosition),
36+
NotificationCenter.default.addObserver(self, selector: #selector(setFramesAndPosition),
3637
name: NSNotification.Name.UIApplicationDidChangeStatusBarOrientation, object: nil)
3738

3839
// Loads the theme if user uses a home quick action
@@ -102,6 +103,7 @@ class MainViewController: UIViewController {
102103

103104
func initializeLables() {
104105
startButton.setTitle("START GAME".localized, for: .normal)
106+
readQRCodeButton.setTitle("READ QR CODE".localized, for: .normal)
105107
instructionsButton.setTitle("INSTRUCTIONS".localized, for: .normal)
106108
settingsButton.setTitle("SETTINGS".localized, for: .normal)
107109
self.navigationItem.title = "Main menu".localized
@@ -118,21 +120,21 @@ class MainViewController: UIViewController {
118120
buttonsHeight = fontSize * 2.0
119121
}
120122

121-
let spaceBetweenButtons = buttonsHeight * 1.6
123+
let spaceBetweenButtons = buttonsHeight * 1.4
122124
let xPosition = (UIScreen.main.bounds.maxX / 2.0) - (buttonsWidth / 2.0)
123125
let yPosition = UIScreen.main.bounds.maxY / 2.0
126+
127+
startButton.frame = CGRect(x: xPosition, y: yPosition - spaceBetweenButtons*1.5, width: buttonsWidth, height: buttonsHeight)
128+
readQRCodeButton.frame = CGRect(x: xPosition, y: yPosition - spaceBetweenButtons/2.0, width: buttonsWidth, height: buttonsHeight)
129+
instructionsButton.frame = CGRect(x: xPosition, y: yPosition + spaceBetweenButtons/2.0, width: buttonsWidth, height: buttonsHeight)
130+
settingsButton.frame = CGRect(x: xPosition, y: yPosition + spaceBetweenButtons*1.5, width: buttonsWidth, height: buttonsHeight)
124131

125132
// ScoreLabel values
126133
let scoreLabelHeight = scoreLabel.frame.height
127134
let scoreLabelWidth = scoreLabel.frame.width
128-
let yPosition2 = (UIScreen.main.bounds.maxY - ((UIScreen.main.bounds.maxY / 2.0) + spaceBetweenButtons + buttonsHeight/2.0))
129-
let yPosition3 = UIScreen.main.bounds.maxY - (yPosition2 / (isPortrait ? 1.3 : 2.0))
130135
let xPosition2 = (UIScreen.main.bounds.maxX / 2.0) - (scoreLabelWidth / 2.0)
131136

132-
scoreLabel.frame = CGRect(x: xPosition2, y: yPosition3, width: scoreLabelWidth, height: scoreLabelHeight)
133-
instructionsButton.frame = CGRect(x: xPosition, y: yPosition, width: buttonsWidth, height: buttonsHeight)
134-
startButton.frame = CGRect(x: xPosition, y: yPosition - spaceBetweenButtons, width: buttonsWidth, height: buttonsHeight)
135-
settingsButton.frame = CGRect(x: xPosition, y: yPosition + spaceBetweenButtons, width: buttonsWidth, height: buttonsHeight)
137+
scoreLabel.frame = CGRect(x: xPosition2, y: yPosition + spaceBetweenButtons*2.5, width: scoreLabelWidth, height: scoreLabelHeight)
136138
}
137139

138140
func loadTheme() {
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import UIKit
2+
import AVFoundation
3+
4+
class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
5+
6+
// MARK: Properties
7+
8+
@IBOutlet weak var allowCameraButton: UIButton!
9+
10+
var videoPreviewLayer: AVCaptureVideoPreviewLayer?
11+
var codeIsRead = false
12+
13+
// MARK: View life cycle
14+
15+
override func viewDidLoad() {
16+
super.viewDidLoad()
17+
18+
allowCameraButton.setTitle("Allow camera access".localized, for: .normal)
19+
20+
if !self.codeIsRead {
21+
22+
let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
23+
let input: AVCaptureInput?
24+
25+
do { input = try AVCaptureDeviceInput(device: captureDevice) }
26+
catch { return }
27+
28+
let captureSession = AVCaptureSession()
29+
captureSession.addInput(input)
30+
31+
let captureMetadataOutput = AVCaptureMetadataOutput()
32+
captureSession.addOutput(captureMetadataOutput)
33+
34+
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
35+
captureMetadataOutput.metadataObjectTypes = [AVMetadataObjectTypeQRCode]
36+
37+
self.videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
38+
self.videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
39+
self.loadPreview()
40+
41+
captureSession.startRunning()
42+
43+
NotificationCenter.default.addObserver(self, selector: #selector(self.loadPreview),
44+
name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
45+
}
46+
}
47+
48+
override func viewWillAppear(_ animated: Bool) {
49+
loadTheme()
50+
}
51+
52+
// MARK: AVCaptureMetadataOutputObjectsDelegate
53+
54+
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
55+
56+
if !codeIsRead {
57+
58+
let metadataObj = metadataObjects.first as! AVMetadataMachineReadableCodeObject
59+
60+
if metadataObj.type == AVMetadataObjectTypeQRCode {
61+
62+
// READ JSON format
63+
64+
if let data = metadataObj.stringValue.data(using: .utf8) {
65+
let content = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [[String: Any]]
66+
performSegue(withIdentifier: "unwindToQuestions", sender: content)
67+
codeIsRead = true
68+
}
69+
}
70+
}
71+
}
72+
73+
// MARK: UIStoryboardSegue Handling
74+
75+
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
76+
77+
if let content = sender as? [[String: Any]], segue.identifier == "unwindToQuestions" {
78+
let controller = segue.destination as! QuestionsViewController
79+
controller.isSetFromJSON = true
80+
controller.set = content as NSArray
81+
}
82+
}
83+
84+
@IBAction func unwindToQRScanner(_ segue: UIStoryboardSegue) { }
85+
86+
@IBAction func allowCameraAction() {
87+
let alertViewController = UIAlertController(title: "Attention".localized,
88+
message: "Camera access required for QR Scanning".localized,
89+
preferredStyle: .alert)
90+
91+
alertViewController.addAction(title: "Cancel".localized, style: .cancel, handler: nil)
92+
alertViewController.addAction(title: "Allow Camera".localized, style: .default) { action in
93+
if let settingsURL = URL(string: UIApplicationOpenSettingsURLString) {
94+
UIApplication.shared.openURL(settingsURL)
95+
}
96+
}
97+
self.present(alertViewController, animated: true, completion: nil)
98+
}
99+
100+
// MARK: Convenience
101+
102+
func loadPreview() {
103+
104+
switch UIDevice.current.orientation {
105+
case .portrait, .faceUp, .faceDown, .portraitUpsideDown, .unknown:
106+
videoPreviewLayer?.connection.videoOrientation = .portrait
107+
case .landscapeRight:
108+
videoPreviewLayer?.connection.videoOrientation = .landscapeLeft
109+
case .landscapeLeft:
110+
videoPreviewLayer?.connection.videoOrientation = .landscapeRight
111+
}
112+
videoPreviewLayer?.frame = view.layer.bounds
113+
114+
if let newLayer = videoPreviewLayer {
115+
view.layer.addSublayer(newLayer)
116+
}
117+
}
118+
119+
func loadTheme() {
120+
let darkThemeEnabled = Settings.sharedInstance.darkThemeEnabled
121+
self.navigationController?.navigationBar.barStyle = darkThemeEnabled ? .black : .default
122+
self.navigationController?.navigationBar.tintColor = darkThemeEnabled ? .orange : .defaultTintColor
123+
view.backgroundColor = darkThemeEnabled ? .gray : .white
124+
allowCameraButton.setTitleColor(darkThemeEnabled ? .warmYellow : .coolBlue, for: .normal)
125+
}
126+
}

Questions/QuestionsViewController.swift

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class QuestionsViewController: UIViewController {
2525
var repeatTimes = UInt8()
2626
var currentTopicIndex = Int()
2727
var currentSetIndex = Int()
28+
var isSetFromJSON = false
2829
var set: NSArray = []
2930
var quiz: NSEnumerator?
3031

@@ -34,7 +35,12 @@ class QuestionsViewController: UIViewController {
3435

3536
super.viewDidLoad()
3637

37-
set = shuffledQuiz(Quiz.quizzes[currentTopicIndex].content)
38+
if !isSetFromJSON {
39+
set = shuffledQuiz(Quiz.quizzes[currentTopicIndex].content)
40+
} else {
41+
set = set.shuffled() as NSArray
42+
}
43+
3844
quiz = set.objectEnumerator()
3945

4046
blurView.frame = UIScreen.main.bounds
@@ -117,6 +123,18 @@ class QuestionsViewController: UIViewController {
117123
return darkThemeEnabled ? .lightContent : .default
118124
}
119125

126+
// MARK: UIStoryboardSegue Handling
127+
128+
@IBAction func unwindToQuestions(_ segue: UIStoryboardSegue) { }
129+
130+
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
131+
if segue.identifier == "unwindToQRScanner" {
132+
let controller = segue.destination as! QRScannerController
133+
controller.codeIsRead = false
134+
Audio.setVolumeLevel(to: Audio.bgMusicVolume)
135+
}
136+
}
137+
120138
// MARK: IBActions
121139

122140
@IBAction func answer1Action() { verify(answer: 0) }
@@ -128,6 +146,14 @@ class QuestionsViewController: UIViewController {
128146
pauseMenuAction()
129147
}
130148

149+
@IBAction func goBackAction() {
150+
if !isSetFromJSON {
151+
performSegue(withIdentifier: "unwindToQuizSelector", sender: self)
152+
} else {
153+
performSegue(withIdentifier: "unwindToQRScanner", sender: self)
154+
}
155+
}
156+
131157
@IBAction func helpAction() {
132158

133159
// Use haptic feedback
@@ -348,7 +374,11 @@ class QuestionsViewController: UIViewController {
348374
}
349375
Settings.sharedInstance.completedSets[currentTopicIndex]?[currentSetIndex] = true
350376

351-
performSegue(withIdentifier: "unwindToQuizSelector", sender: self)
377+
if !isSetFromJSON {
378+
performSegue(withIdentifier: "unwindToQuizSelector", sender: self)
379+
} else {
380+
performSegue(withIdentifier: "unwindToMainMenu", sender: self)
381+
}
352382
}
353383

354384
func repeatActionDetailed() {

Questions/es.lproj/Localizable.strings

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
"Main menu" = "Menú principal";
1010

1111
"START GAME" = "COMENZAR";
12+
"READ QR CODE" = "LEER CÓDIGO QR";
13+
// "We need to access your camera for scanning QR code." = "Necesitamos acceder a la cámara para escanear el código QR.";
14+
"Camera access required for QR Scanning" = "Acceso a cámara requerido para escanear el código QR";
15+
"Allow camera access" = "Permita acceso a la cámara";
16+
"Allow Camera" = "Permitir cámara";
17+
1218

1319
"INSTRUCTIONS" = "INSTRUCCIONES";
1420
"Instructions" = "Instrucciones";

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Questions
22
[![Swift version](https://img.shields.io/badge/Swift-3-orange.svg)](https://swift.org/download)
3-
[![Version](https://img.shields.io/badge/version-v2.11.1--beta-green.svg)](https://github.com/illescasDaniel/Questions/releases)
3+
[![Version](https://img.shields.io/badge/version-v2.12--beta-green.svg)](https://github.com/illescasDaniel/Questions/releases)
44
[![license](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)](https://github.com/illescasDaniel/Questions/blob/master/LICENCE)
55

66
Prototype of a Quiz app for iOS.

0 commit comments

Comments
 (0)