on master help sign in
15 files changed+443−39
since splitsnapshots
unifiedsplit
Melk.xcodeproj/project.pbxproj+14−2 Melk.xcodeproj/xcuserdata/ilya.xcuserdatad/xcschemes/Melk.xcscheme+3−3 Melk/CardContainer.swift+100 Melk/CardView.swift+126−11 Melk/CollectionView.swift+4−2 Melk/MainCoordinator.swift+8−2 Melk/MainViewController.swift+22−2 Melk/SecondaryCoordinator.swift+12 Melk/SecondaryViewController.swift+111−2 Melk/StackView.swift+3−1 Melk/StreamingParser.swift+1−1 Melk/TableViewCells.swift+12−3 Melk/VKData.swift+8−8 Melk/VKServer.swift+2−2 README.md+17
Melk.xcodeproj/project.pbxproj+14−2
@@ -10,6 +10,7 @@
1010 0919AEA71F0D3D9D00894FFA /* SecondaryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */; };
1111 0919AEA91F0D459600894FFA /* CardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEA81F0D459600894FFA /* CardView.swift */; };
1212 0919AEAD1F0D706700894FFA /* StackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEAC1F0D706700894FFA /* StackView.swift */; };
13+ 09231E351F2394ED007C836D /* CardContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09231E341F2394ED007C836D /* CardContainer.swift */; };
1314 09939A561F097DA90014719D /* TableViewCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09939A551F097DA90014719D /* TableViewCells.swift */; };
1415 09A456361F0ED9A800BAC32D /* CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A456351F0ED9A800BAC32D /* CollectionView.swift */; };
1516 09AFAB041F159C9200EBD200 /* StreamingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AFAB031F159C9200EBD200 /* StreamingService.swift */; };
@@ -69,6 +70,7 @@
6970 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondaryViewController.swift; sourceTree = "<group>"; };
7071 0919AEA81F0D459600894FFA /* CardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = "<group>"; };
7172 0919AEAC1F0D706700894FFA /* StackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackView.swift; sourceTree = "<group>"; };
73+ 09231E341F2394ED007C836D /* CardContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardContainer.swift; sourceTree = "<group>"; };
7274 09939A551F097DA90014719D /* TableViewCells.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCells.swift; sourceTree = "<group>"; };
7375 09A456351F0ED9A800BAC32D /* CollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionView.swift; sourceTree = "<group>"; };
7476 09AFAAFF1F159AE300EBD200 /* SwiftyJSON.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyJSON.framework; path = Carthage/Build/iOS/SwiftyJSON.framework; sourceTree = SOURCE_ROOT; };
@@ -117,11 +119,21 @@
117119/* End PBXFrameworksBuildPhase section */
118120
119121/* Begin PBXGroup section */
122+ 09231E331F2394C5007C836D /* Card */ = {
123+ isa = PBXGroup;
124+ children = (
125+ 0919AEA81F0D459600894FFA /* CardView.swift */,
126+ 09231E341F2394ED007C836D /* CardContainer.swift */,
127+ );
128+ name = Card;
129+ sourceTree = "<group>";
130+ };
120131 098397F71F1E887F008C88DD /* Main */ = {
121132 isa = PBXGroup;
122133 children = (
123134 09BF420F1F0833C100AFAD57 /* MainCoordinator.swift */,
124135 09BF42551F08413000AFAD57 /* MainViewController.swift */,
136+ 09939A551F097DA90014719D /* TableViewCells.swift */,
125137 );
126138 name = Main;
127139 sourceTree = "<group>";
@@ -176,12 +188,11 @@
176188 098397F71F1E887F008C88DD /* Main */,
177189 098397F81F1E888F008C88DD /* Secondary */,
178190 098397F91F1E88A3008C88DD /* Streaming */,
191+ 09231E331F2394C5007C836D /* Card */,
179192 09BF41EE1F07F71F00AFAD57 /* AppDelegate.swift */,
180193 09BF42111F08341B00AFAD57 /* MyWeirdness.swift */,
181194 09BF42121F08341B00AFAD57 /* PublicCrap.swift */,
182195 09F03F351F1674ED000CE916 /* VKServer.swift */,
183− 0919AEA81F0D459600894FFA /* CardView.swift */,
184− 09939A551F097DA90014719D /* TableViewCells.swift */,
185196 09BF420D1F0833A700AFAD57 /* LocalVKDataTypes.swift */,
186197 09BF42151F0834D400AFAD57 /* VKPublicCrap.swift */,
187198 09BF42171F08352E00AFAD57 /* VKData.swift */,
@@ -294,6 +305,7 @@
294305 09BF42181F08352E00AFAD57 /* VKData.swift in Sources */,
295306 09BF42101F0833C100AFAD57 /* MainCoordinator.swift in Sources */,
296307 09A456361F0ED9A800BAC32D /* CollectionView.swift in Sources */,
308+ 09231E351F2394ED007C836D /* CardContainer.swift in Sources */,
297309 09BF42131F08341B00AFAD57 /* MyWeirdness.swift in Sources */,
298310 0919AEA91F0D459600894FFA /* CardView.swift in Sources */,
299311 09BF42321F08388100AFAD57 /* SecondaryCoordinator.swift in Sources */,
Melk.xcodeproj/xcuserdata/ilya.xcuserdatad/xcschemes/Melk.xcscheme+3−3
@@ -42,9 +42,9 @@
4242 </AdditionalOptions>
4343 </TestAction>
4444 <LaunchAction
45− buildConfiguration = "Debug"
46− selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
47− selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
45+ buildConfiguration = "Release"
46+ selectedDebuggerIdentifier = ""
47+ selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
4848 launchStyle = "0"
4949 useCustomWorkingDirectory = "NO"
5050 ignoresPersistentStateOnLaunch = "NO"
Melk/CardContainer.swiftnew file+100
@@ -0,0 +1,100 @@
1+//
2+// CardContainer.swift
3+// Melk
4+//
5+// Created by Ilya Kos on 7/22/17.
6+// Copyright © 2017 Ilya Kos. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class CardContainer: UIView {
12+
13+ func animate() {
14+ blurView.effect = blur
15+ card.transform = smallTransform
16+ card.layer.opacity = 0
17+ shadowView.layer.opacity = 0
18+ shadowView.transform = smallTransform
19+ shadowView.layer.shadowRadius = 0
20+ UIView.animate(withDuration: 0.5, delay: 0, options: [.curveEaseInOut], animations: {
21+ self.card.transform = self.normalTransform
22+ self.shadowView.transform = self.normalTransform
23+ self.shadowView.layer.shadowRadius = self.shadowRadius
24+ self.shadowView.layer.opacity = 1
25+ self.blurView.effect = nil
26+ self.card.layer.opacity = 1
27+ }, completion: nil)
28+ }
29+
30+ // MARK: Passthrough to CardView
31+ var post: WallPost? {
32+ get {
33+ return card.post
34+ }
35+ set {
36+ card.post = newValue
37+ }
38+ }
39+
40+ var minWidth: CGFloat {
41+ get {
42+ return card.minWidth
43+ }
44+ set {
45+ card.minWidth = newValue
46+ }
47+ }
48+
49+
50+ var height: CGFloat {
51+ get {
52+ return card.height
53+ }
54+ set {
55+ card.height = newValue
56+ }
57+ }
58+
59+ private let card: CardView
60+ private let blurView = UIVisualEffectView()
61+ private let shadowView = UIView()
62+ private let blur = UIBlurEffect(style: UIBlurEffectStyle.light)
63+ private let scale: CGFloat = 0.5
64+ private let smallTransform: CGAffineTransform
65+ private let normalTransform = CGAffineTransform(scaleX: 1, y: 1)
66+ private let shadowRadius: CGFloat = 12
67+ private let shadowOpacity: Float = 0.15
68+ private let shadowOffset: CGFloat = 16
69+
70+ override init(frame: CGRect) {
71+ card = CardView(frame: frame)
72+ smallTransform = CGAffineTransform(scaleX: scale, y: scale)
73+ super.init(frame: frame)
74+ shadowView.layer.shadowColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1).cgColor
75+ shadowView.layer.shadowRadius = shadowRadius
76+ shadowView.layer.shadowOpacity = shadowOpacity
77+ shadowView.backgroundColor = UIColor.clear
78+ shadowView.clipsToBounds = false
79+ shadowView.layer.shadowOffset = CGSize.zero
80+ addSubview(shadowView)
81+ addSubview(card)
82+ addSubview(blurView)
83+ }
84+
85+ required init?(coder aDecoder: NSCoder) {
86+ fatalError("init(coder:) has not been implemented")
87+ }
88+
89+ override func layoutSubviews() {
90+ super.layoutSubviews()
91+ card.layoutIfNeeded()
92+ self.frame = card.frame
93+ shadowView.layer.shadowPath = CGPath(roundedRect: card.frame, cornerWidth: card.layer.cornerRadius, cornerHeight: card.layer.cornerRadius,
94+ transform: nil)
95+ shadowView.layer.shadowOffset.height = shadowOffset
96+ shadowView.frame = card.frame
97+ blurView.frame = CGRect(x: -shadowRadius, y: 0, width: card.frame.width + shadowRadius*2, height: card.frame.height + shadowRadius + shadowOffset)
98+ }
99+
100+}
Melk/CardView.swift+126−11
@@ -12,21 +12,51 @@ class CardView: UIView {
1212
1313 var post: WallPost? {
1414 didSet {
15− print(post?.owner)
15+// print(post?.owner)
1616 update()
1717 }
1818 }
19+ var minWidth: CGFloat = 300 {
20+ didSet {
21+// update()
22+ }
23+ }
24+ var height: CGFloat = 400 {
25+ didSet {
26+// update()
27+ }
28+ }
1929
2030 override init(frame: CGRect) {
2131 super.init(frame: frame)
2232 layer.masksToBounds = true
2333 layer.cornerRadius = 20
24− backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
34+ gradient.colors = [#colorLiteral(red: 0.9841352105, green: 0.9841352105, blue: 0.9841352105, alpha: 1).cgColor, #colorLiteral(red: 0.9456828237, green: 0.9456828237, blue: 0.9456828237, alpha: 1).cgColor]
35+ gradient.startPoint = CGPoint(x: 0.5, y: 0)
36+ gradient.endPoint = CGPoint(x: 0.5, y: 1)
37+ layer.addSublayer(gradient)
38+// backgroundColor = #colorLiteral(red: 0.9638966278, green: 0.9734401588, blue: 0.9734401588, alpha: 1)
2539 }
2640
2741 required init?(coder aDecoder: NSCoder) {
2842 fatalError("init(coder:) has not been implemented")
2943 }
44+
45+ private let gradient = CAGradientLayer()
46+
47+
48+ private let padding = CGPoint(x: 32, y: 32)
49+ private let vPadding1: CGFloat = 16
50+ private let vPadding2: CGFloat = 16
51+ private var minTextHeight: CGFloat {
52+ return CGFloat(height * 0.2)
53+ }
54+ private let gradientRatio: CGFloat = 0.2
55+ private let minGradientAmount: CGFloat = 70
56+ private let hiddenByGradient: CGFloat = 10
57+ private var maxAttachmentDim: CGFloat {
58+ return minWidth / 2.3
59+ }
3060
3161// private let avatar = UIImage()
3262// private let name = UIImage()
@@ -35,14 +65,13 @@ class CardView: UIView {
3565 subviews.map {$0.removeFromSuperview()}
3666
3767 let largeStack = StackView(axis: .vertical, alignment: .left)
38− largeStack.padding = CGPoint(x: 32, y: 32)
68+ largeStack.padding = padding
3969 addSubview(largeStack)
4070
4171 let userView = StackView(axis: .horizontal, alignment: .center)
42−// userView.backgroundColor = #colorLiteral(red: 0.5568627715, green: 0.3529411852, blue: 0.9686274529, alpha: 1)
4372
4473
45− let avatarDim: CGFloat = 50
74+ let avatarDim: CGFloat = 64
4675 let avatar = UIImageView()
4776 post?.owner?.avatar?.load(into: avatar)
4877 avatar.layer.cornerRadius = avatarDim/2
@@ -52,30 +81,116 @@ class CardView: UIView {
5281 avatar.frame.size = CGSize(width: avatarDim, height: avatarDim)
5382 userView.addArrangedSubview(.view(avatar))
5483
55− userView.addArrangedSubview(.spacer(8))
84+ userView.addArrangedSubview(.spacer(16))
5685
5786 let userName = UILabel()
5887 let firstName = post?.owner?.firstName ?? ""
5988 let lastName = post?.owner?.lastName ?? ""
6089 userName.text = "\(firstName) \(lastName)"
6190 userName.font = UIFont.systemFont(ofSize: 28, weight: UIFontWeightHeavy)
91+ userName.textColor = UIColor.darkText
6292 userName.sizeToFit()
6393 userView.addArrangedSubview(.view(userName))
64− userView.frame.origin = CGPoint(x: 16, y: 16)
94+
95+ userView.layoutIfNeeded()
6596
6697 largeStack.addArrangedSubview(.view(userView))
6798
68− largeStack.addArrangedSubview(.spacer(16))
99+ largeStack.addArrangedSubview(.spacer(vPadding1))
100+
101+
102+ let width = max(minWidth - 2 * padding.x, userView.frame.width)
103+
104+
105+ let photoView = CollectionView()
106+ photoView.alignment = .top
107+ photoView.horizontalPadding = 8
108+ photoView.verticalPadding = 8
109+ photoView.padding = CGPoint.zero
110+ photoView.maxWidth = width
111+ var maxPhotoDim: CGFloat = maxAttachmentDim
112+ if let attachments = post?.attachments {
113+ for attachment in attachments {
114+ if let v = attachment.value {
115+ switch v {
116+ case .photo(let photo):
117+ if var pWidth = photo.width.value >>> {CGFloat($0)},
118+ var pHeight = photo.height.value >>> {CGFloat($0)} {
119+ let photoV = UIImageView()
120+ photoV.layer.minificationFilter = kCAFilterTrilinear
121+ photoV.contentMode = .scaleAspectFill
122+ photo.image?.load(into: photoV)
123+ var k: CGFloat!
124+ if pWidth > pHeight {
125+ k = CGFloat(pWidth) / maxPhotoDim
126+ } else {
127+ k = CGFloat(pHeight) / maxPhotoDim
128+ }
129+ pWidth /= k
130+ pHeight /= k
131+ photoV.frame.size = CGSize(width: pWidth, height: pHeight)
132+ photoView.addArrangedSubview(photoV)
133+ }
134+
135+ default:
136+ break // TODO: Handle other data types
137+ }
138+ }
139+ }
140+ }
141+ photoView.layoutIfNeeded()
142+
143+ var textHeight = height
144+ textHeight -= padding.y * 2 + vPadding1 + userView.frame.height
145+ if let count = post?.attachments.count{
146+ if count > 0 {
147+ textHeight -= vPadding2 + photoView.frame.height
148+ }
149+ }
150+ print(textHeight)
151+ textHeight = max(textHeight, minTextHeight)
152+ print(textHeight)
69153
70154 let body = UILabel()
71155 body.numberOfLines = 0
72− let bodyWidth: Double = 300
156+ let bodyWidth: Double = Double(width)
73157 body.font = UIFont.systemFont(ofSize: 22, weight: UIFontWeightSemibold)
74− body.text = post?.body ?? "Spicy jalapeno flank landjaeger sausage tongue tail fatback frankfurter. Meatball tongue tail short ribs pastrami. Short ribs chicken pig tenderloin sausage pork. Corned beef t-bone pancetta jerky chuck picanha venison, turducken tenderloin shoulder boudin drumstick hamburger. Pork meatball meatloaf prosciutto pig andouille. Sirloin bacon hamburger short ribs pork cupim shank biltong turducken bresaola kevin prosciutto shoulder meatball. Strip steak frankfurter pork chop biltong jowl beef rump venison pancetta turkey tongue."
158+ body.text = post?.body
159+ body.textColor = UIColor.darkText
75160 let size = body.textRect(forBounds: CGRect(x: 0, y: 0, width: bodyWidth, height: Double.infinity), limitedToNumberOfLines: 0).size
76− body.frame.size = size
161+ body.frame.size.width = size.width
162+ if size.height > textHeight {
163+ body.frame.size.height = textHeight
164+ let maskView = UIView()
165+ let maskLayer = CAGradientLayer()
166+ maskLayer.colors = [UIColor.black.cgColor, UIColor.clear.cgColor]
167+ maskLayer.endPoint = CGPoint(x: 0.5, y: 1 - (hiddenByGradient/textHeight))
168+ let minPoint = minGradientAmount / textHeight
169+ maskLayer.startPoint = CGPoint(x: 0.5, y: 1 - max(minPoint, gradientRatio))
170+ print(maskLayer.startPoint)
171+ maskLayer.frame = body.bounds
172+ maskView.frame = body.bounds
173+ maskView.layer.addSublayer(maskLayer)
174+ body.mask = maskView
175+ } else {
176+ body.frame.size.height = size.height
177+ }
77178
78179 largeStack.addArrangedSubview(.view(body))
79180
181+ if let count = post?.attachments.count{
182+ if count > 0 {
183+ largeStack.addArrangedSubview(.spacer(vPadding2))
184+ largeStack.addArrangedSubview(.view(photoView))
185+ }
186+ }
187+
188+ largeStack.layoutIfNeeded()
189+
190+ frame.size = CGSize(width: width + padding.x * 2, height: min(height, largeStack.frame.height))
191+
192+ gradient.frame = self.bounds
193+ gradient.endPoint = CGPoint(x: 0.5, y: bounds.height / height)
194+
80195 }
81196}
Melk/CollectionView.swift+4−2
@@ -68,7 +68,7 @@ class CollectionView: UIView {
6868 var firstStack = true
6969
7070 for view in arrangedSubviews {
71− print("new view")
71+// print("new view")
7272 if firstView {
7373 lastStack.addArrangedSubview(.view(view))
7474 firstView = false
@@ -94,7 +94,9 @@ class CollectionView: UIView {
9494 }
9595 }
9696 lastStack.layoutIfNeeded()
97− largeStack.addArrangedSubview(.spacer(verticalPadding))
97+ if !firstStack {
98+ largeStack.addArrangedSubview(.spacer(verticalPadding))
99+ }
98100 largeStack.addArrangedSubview(.view(lastStack))
99101 largeStack.frame.origin = CGPoint.zero
100102 largeStack.layoutIfNeeded()
Melk/MainCoordinator.swift+8−2
@@ -27,6 +27,14 @@ class MainCoordinator {
2727 deleteRealmIfMigrationNeeded: true)
2828
2929 navC = UINavigationController(rootViewController: mainVC)
30+
31+ mainVC.present = { tags in
32+ if tags.count > 0 {
33+ self.secondaryCoord.present(tag: tags.first!)
34+ self.stream.rules = tags
35+ self.stream.connect()
36+ }
37+ }
3038 updateScreens()
3139 stream.handleResult = { result in
3240 self.secondaryCoord.add(result: result)
@@ -44,8 +52,6 @@ class MainCoordinator {
4452// }
4553 print("didAuthorize")
4654 window.rootViewController = navC
47− stream.rules = ["я", "а"]
48− stream.connect()
4955 }
5056
5157 func updateScreens() {
Melk/MainViewController.swift+22−2
@@ -29,7 +29,9 @@ class MainViewController: UIViewController {
2929 }
3030
3131 @objc private func presentTags() {
32−
32+ present?(
33+ mainView.tags.flatMap({$0}).filter({$0 != ""})
34+ )
3335 }
3436}
3537
@@ -45,6 +47,8 @@ class MainView: UIView {
4547 tableView.rowHeight = UITableViewAutomaticDimension
4648 tableView.register(TitleTableViewCell.self, forCellReuseIdentifier: TitleTableViewCell.identifier)
4749 tableView.register(TagTableViewCell.self, forCellReuseIdentifier: TagTableViewCell.identifier)
50+ tableView.separatorStyle = .none
51+ tableView.allowsSelection = false
4852 setNeedsLayout()
4953 }
5054
@@ -52,6 +56,17 @@ class MainView: UIView {
5256 tableView.frame = bounds
5357 }
5458
59+ fileprivate var tags: [String?] = [nil] {
60+ didSet {
61+ if tags.last! != nil && tags.last! != "" {
62+ if tags.count < 10 {
63+ tags.append(nil)
64+ tableView.insertRows(at: [IndexPath.init(row: tags.count - 1, section: 0)], with: .automatic)
65+ }
66+ }
67+ }
68+ }
69+
5570 required init?(coder aDecoder: NSCoder) {
5671 fatalError("init(coder:) has not been implemented")
5772 }
@@ -62,13 +77,18 @@ extension MainView: UITableViewDataSource {
6277 if let cell = tableView.dequeueReusableCell(withIdentifier: TagTableViewCell.identifier) as? TagTableViewCell {
6378// cell.titleText = "Testing testing 1 2 3"
6479 cell.style = indexPath.item == 0 ? .large : .normal
80+ let index = indexPath.row
81+ cell.tagDidChange = { tag in
82+ self.tags[index] = tag
83+ }
84+ cell.tagString = tags[index]
6585 return cell
6686 }
6787 return UITableViewCell()
6888 }
6989
7090 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
71− return 3
91+ return tags.count
7292 }
7393
7494 func numberOfSections(in tableView: UITableView) -> Int {
Melk/SecondaryCoordinator.swift+12
@@ -12,12 +12,21 @@ class SecondaryCoordinator {
1212
1313 var windows: [UIWindow] = []
1414
15+ private var didStartPresenting = false
16+ private var tag: String = ""
17+
1518 func add(result: StreamingServiceResult) {
1619 for vc in VCs {
1720 vc.add(result: result)
1821 }
1922 }
2023
24+ func present(tag newTag: String) {
25+ didStartPresenting = true
26+ self.tag = newTag
27+ VCs.map({ $0.present(tag: newTag) })
28+ }
29+
2130 init() {
2231
2332 }
@@ -36,6 +45,9 @@ class SecondaryCoordinator {
3645 VCs.append(VC)
3746 window.rootViewController = VC
3847 windows.append(window)
48+ if didStartPresenting {
49+ VC.present(tag: tag)
50+ }
3951 }
4052 }
4153}
Melk/SecondaryViewController.swift+111−2
@@ -19,6 +19,10 @@ class SecondaryViewController: UIViewController {
1919 sView.add(result: result)
2020 }
2121
22+ func present(tag: String) {
23+ sView.present(tag: tag)
24+ }
25+
2226 required init?(coder aDecoder: NSCoder) {
2327 fatalError("init(coder:) has not been implemented")
2428 }
@@ -27,18 +31,123 @@ class SecondaryViewController: UIViewController {
2731}
2832
2933class SecondaryView: UIView {
34+
35+ var mainTag: String? {
36+ get {
37+ return tagLabel.text
38+ }
39+ set {
40+ tagLabel.text = newValue
41+
42+ }
43+ }
44+
45+
46+ private let spacer: CGFloat = 32
47+ private let bottomSpacer: CGFloat = 64
48+ private let tagSpacer: CGFloat = 32
49+ private let tagLabel = UILabel()
50+ private let melkLabel = UILabel()
51+
52+ func present(tag: String) {
53+ UIView.perform(.delete, on: [melkLabel], options: [], animations: nil) { (_) in
54+ self.melkLabel.removeFromSuperview()
55+ self.mainTag = tag
56+ self.tagLabel.sizeToFit()
57+ self.layoutIfNeeded(animated: true)
58+ }
59+ }
60+
3061 func add(result: StreamingServiceResult) {
3162 switch result {
3263 case .post(let post):
64+ let card = CardContainer()
65+ card.minWidth = min(bounds.width / 2.3, 550)
66+ card.height = cardHeight
3367 card.post = post
68+ cards.addArrangedSubview(.spacer(spacer))
69+ cards.addArrangedSubview(.view(card))
70+ card.animate()
3471 }
72+ self.layoutIfNeeded(animated: true)
73+// self.setNeedsLayout(animated: true) // Doesn't work for some reason
3574 }
3675
37− let card = CardView(frame: CGRect(x: 20, y: 20, width: 600, height: 300))
76+// private let card = CardView()
77+ private let cards = StackView(axis: .horizontal, alignment: .top)
3878
3979 override init(frame: CGRect) {
4080 super.init(frame: frame)
41− addSubview(card)
81+ backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
82+ addSubview(cards)
83+
84+ melkLabel.text = "Melk"
85+ melkLabel.font = UIFont.systemFont(ofSize: 200, weight: UIFontWeightHeavy)
86+ melkLabel.textColor = #colorLiteral(red: 0.9601849914, green: 0.9601849914, blue: 0.9601849914, alpha: 1)
87+ addSubview(melkLabel)
88+
89+ tagLabel.font = UIFont.systemFont(ofSize: 69, weight: UIFontWeightBold)
90+ tagLabel.textColor = #colorLiteral(red: 0.3176470588, green: 0.3179988265, blue: 0.3179988265, alpha: 1)
91+ addSubview(tagLabel)
92+ }
93+
94+ func setNeedsLayout(animated: Bool) {
95+ self.animate = animated
96+ self.setNeedsLayout()
97+ }
98+ func layoutIfNeeded(animated: Bool) {
99+ self.animate = animated
100+ self.layoutIfNeeded()
101+ }
102+ private var animate: Bool = false
103+
104+ private func cleanCards() {
105+ var offset = 0
106+ outLoop: for (i, element) in cards.arrangedSubviews.enumerated() {
107+ switch element {
108+ case .view(let card):
109+ let cardFrame = convert(card.frame, from: cards)
110+ if cardFrame.intersects(bounds) {
111+ break outLoop
112+ } else {
113+ cards.arrangedSubviews.remove(at: i-1-offset)
114+ cards.arrangedSubviews.remove(at: i-1-offset)
115+ offset += 2
116+ }
117+ case .spacer(_):
118+ break
119+ }
120+ }
121+ }
122+
123+ private let padding = CGPoint(x: 32, y: 16)
124+ private var cardHeight: CGFloat = 400
125+
126+ override func layoutSubviews() {
127+ super.layoutSubviews()
128+
129+ melkLabel.sizeToFit()
130+ melkLabel.center = center
131+
132+ let updates = {
133+ self.tagLabel.sizeToFit()
134+ self.tagLabel.center.x = self.center.x
135+ self.tagLabel.frame.origin.y = self.tagSpacer
136+ self.cards.frame.origin.y = self.tagLabel.frame.maxY + self.padding.y
137+ self.cards.layoutIfNeeded()
138+ self.cards.frame.origin.x = self.frame.width - self.padding.x - self.cards.frame.width
139+ }
140+ if animate {
141+ UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.6, options: [], animations: updates, completion: { _ in
142+ self.cleanCards()
143+ })
144+// UIView.animate(withDuration: 0.4, delay: 0.0, options: [.curveEaseOut], animations: updates, completion: nil)
145+ } else {
146+ updates()
147+ self.cleanCards()
148+ }
149+ cardHeight = bounds.height - tagLabel.frame.height - padding.y - tagSpacer - bottomSpacer
150+ animate = false
42151 }
43152
44153 required init?(coder aDecoder: NSCoder) {
Melk/StackView.swift+3−1
@@ -44,6 +44,8 @@ class StackView: UIView {
4444 super.layoutSubviews()
4545 print("laying out subviews")
4646
47+ subviews.map({$0.removeFromSuperview()}) // TODO: There must be a better way!
48+
4749 var height: CGFloat = 0
4850 var width: CGFloat = 0
4951 switch axis {
@@ -62,7 +64,7 @@ class StackView: UIView {
6264 height += a
6365 }
6466 case .view(let view):
65−
67+ view.layoutIfNeeded()
6668 switch axis {
6769 case .horizontal:
6870 view.frame.origin.x = width
Melk/StreamingParser.swift+1−1
@@ -50,7 +50,7 @@ class StreamingParser {
5050 let complete = {
5151 comp -= 1
5252 if comp == 0 {
53− print(post.owner)
53+// print(post.owner)
5454 completion(.post(post))
5555 }
5656 }
Melk/TableViewCells.swift+12−3
@@ -57,6 +57,12 @@ class TagTableViewCell: UITableViewCell {
5757 }
5858 }
5959
60+ var tagDidChange: ((String?) -> ())?
61+
62+ @objc private func textDidChange() {
63+ tagDidChange?(tagString)
64+ }
65+
6066 var style: TagStyle = .normal {
6167 didSet {
6268 updateStyle()
@@ -70,6 +76,9 @@ class TagTableViewCell: UITableViewCell {
7076
7177 textField.borderStyle = .roundedRect
7278 textField.adjustsFontSizeToFitWidth = false
79+ textField.autocapitalizationType = .none
80+ textField.autocorrectionType = .no
81+ textField.addTarget(self, action: #selector(self.textDidChange), for: .editingChanged)
7382
7483 contentView.addSubview(textField)
7584// textField.delegate = self
@@ -92,7 +101,7 @@ class TagTableViewCell: UITableViewCell {
92101 CGRect.infinite, limitedToNumberOfLines: 1)
93102 .height + 2*2
94103
95− let frame = CGRect(x: 16, y: 8, width: bounds.width - 16*2, height: height)
104+ let frame = CGRect(x: 16, y: style == .large ? 32 : 4, width: bounds.width - 16*2, height: height)
96105 textField.frame = frame
97106 print(textField.frame)
98107 }
@@ -103,8 +112,8 @@ class TagTableViewCell: UITableViewCell {
103112
104113
105114 static let identifier = "io.Melk.tagTableViewCell"
106− static let largeHeight: CGFloat = 37.5 + 8*2
107− static let normalHeight: CGFloat = 28.0 + 8*2
115+ static let largeHeight: CGFloat = 37.5 + 16 + 32
116+ static let normalHeight: CGFloat = 28.0 + 4*2
108117}
109118
110119//extension TagTableViewCell: UITextFieldDelegate {
Melk/VKData.swift+8−8
@@ -49,7 +49,7 @@ class VKData {
4949 }
5050 user.birthday = formatter.date(from: birthday)
5151 }
52− user.avatar = RealmPhoto.by(user.avatarID, surl: json["photo_max"].string)
52+ user.avatar = RealmPhoto.by(user.avatarID, surl: json["photo_100"].string)// was "photo_max"
5353 user.photo = RealmPhoto.by(user.photoID, surl: json["photo_max_orig"].string)
5454 if let friendStat = json["friend_status"].int {
5555 user.friendStatus = FriendStatus(rawValue: friendStat)
@@ -289,13 +289,13 @@ class VKData {
289289 photo.width.value = json["width"].int
290290 photo.height.value = json["height"].int
291291 var surl: String?
292− if let imageSurl = json["photo_2560"].string {
293− surl = imageSurl
294− } else if let imageSurl = json["photo_1280"].string {
295− surl = imageSurl
296− } else if let imageSurl = json["photo_807"].string {
297− surl = imageSurl
298− } else if let imageSurl = json["photo_604"].string {
292+// if let imageSurl = json["photo_2560"].string {
293+// surl = imageSurl
294+// } else if let imageSurl = json["photo_1280"].string {
295+// surl = imageSurl
296+// } else if let imageSurl = json["photo_807"].string {
297+// surl = imageSurl
298+ /*} else*/ if let imageSurl = json["photo_604"].string {
299299 surl = imageSurl
300300 } else if let imageSurl = json["photo_130"].string {
301301 surl = imageSurl
Melk/VKServer.swift+2−2
@@ -81,7 +81,7 @@ class VKServer {
8181 let id = user.id
8282 let params: Parameters = [
8383 "user_ids": id,
84− "fields": "photo_max"
84+ "fields": "photo_100"
8585 ]
8686 Alamofire.request(url(for: "users.get"), parameters: params)
8787 .validate().responseData { (response) in
@@ -118,6 +118,6 @@ class VKServer {
118118}
119119
120120fileprivate func url(`for` method: String) -> URL {
121− let surl = "https://api.vk.com/method/\(method)?access_token=\(accessKey)&v=\(vkAPIVersion)"
121+ let surl = "https://api.vk.com/method/\(method)?access_token=\(accessKey)&v=\(vkAPIVersion)&lang=0"
122122 return URL.init(string: surl)!
123123}
README.mdnew file+17
@@ -0,0 +1,17 @@
1+# Melk
2+
3+_Might not work properly with new streaming API. This project was developed while the API was in beta._
4+
5+[![Melk demo](http://img.youtube.com/vi/9SNc0bLDCL0/0.jpg)](http://www.youtube.com/watch?v=9SNc0bLDCL0)
6+
7+При проведении различных мероприятий организаторы довольно часто призывают людей, посещающих эти мероприятия, выложить у себя в соц сети что-нибудь об этом мероприятии, чаще всего под мероприятие даже выделяется отдельный хештег.
8+
9+Для привлечения внимания к хештегу и поощрения его использования можно выводить некоторые из выложенных с хештегом записей туда, где все смогут это увидеть.
10+
11+При проведении больших мероприятий желательно использовать большое количество независимых систем, чтобы если летит что-то одно, все остальное продолжало работать.
12+
13+Полноценный десктопный компьютер для данной цели не нужен, поэтому приложение работает просто на телефоне или планшете.
14+
15+Самый первый хештег или ключевое слово будет крупно показано вместе со всеми записями.
16+
17+После того, как будет нажата кнопка “Present”, записи с основным хештегом будут выводиться на любые подключенные к устройству экраны.