After the classmate’s suggestion, found that the writing is really a little messy, take advantage of the time before going to work to tidy up the module
Recently, taking advantage of the project demand is not very tight, I used the spare time to complete the function to write a best project into swift version. I have to say, SWIFT version drives me crazy, the strict grammar structure is always broken, it is almost killing me. I didn’t learn Swift for a long time. I read some basic knowledge points in bits and pieces, and found that reading documents was boring, so I tried to rewrite the existing project by myself. The process was painful and the harvest was really great.
Here I mainly through the essence, release, attention, login, my and recommended attention interface for a few modules of the simple implementation of the introduction, because the code has been knocking for a year, language ability has been degraded, the expression is not good directly on the code, hey hey ~~~ ~
The overall framework construction of the project
Written all know of the project, the most commonly used is the framework of TabBarController + NavigationController form, here is the classic style, still used concrete implementation and OC is same, also includes the custom tabbar, show part of the code on the following:
Block of code:
private func setupChildVcs() {
setupChildVc(vc: EssenseController(),title:"Essence",image:"tabBar_essence_icon",selectedImage:"tabBar_essence_click_icon")
setupChildVc(vc: NewViewController(),title:"New post",image:"tabBar_new_icon",selectedImage:"tabBar_new_click_icon")
setupChildVc(vc: FriendThrendController(),title:"Attention",image:"tabBar_friendTrends_icon",selectedImage:"tabBar_friendTrends_click_icon")
setupChildVc(vc: ProfileController(),title:"我",image:"tabBar_me_icon",selectedImage:"tabBar_me_click_icon")
}
private func setupTabBar() {
setValue(TabBar(), forKeyPath: "tabBar");
}
private func setupChildVc(vc:UIViewController,title:String,image:String,selectedImage:String) {
let nav = NavigationController(rootViewController: vc)
addChildViewController(nav)
nav.tabBarItem.title = title
nav.tabBarItem.image = UIImage(named: image)
nav.tabBarItem.selectedImage = UIImage(named: selectedImage)
}
Copy the code
This is mainly about building the overall framework. One thing I think is quite good is adding a plus button to the custom Tabbar
override func layoutSubviews() { super.layoutSubviews() publishButton? .center = CGPoint(x: width * 0.5, y: height * 0.5)let buttonY:CGFloat = 0
let buttonW = width / 5
let buttonH = height
var index:Int = 0
var buttonX:CGFloat = 0
for button in subviews {
if! button.isKind(of: NSClassFromString("UITabBarButton")!){
continue
}
buttonX = buttonW * CGFloat((index > 1 ? index + 1 : index))
button.frame = CGRect(x: buttonX, y: buttonY, width: buttonW, height: buttonH)
index += 1
}
publishButton?.addTarget(self, action: #selector(TabBar.publishButtonClick), for: .touchUpInside)
}
Copy the code
But in the process of writing is more helpless pain, different types of constants in swift is not for arithmetic, must be converted to the same type can be, to this point in the process of writing is not here right now, but fortunately the Xcode directly error warning, can timely correction, so when it comes to this, I want to say is, write down to complete a small project, I learned almost as I wrote, and swift’s grammatical rigor was maddening and exhilarating
The overall framework is introduced, and the following modules are introduced:
2. Essence/new post module
1. Network request and data analysis
This module is mainly to show the list, the form of the list includes video, sound, pictures and the mixture of paragraphs, so it needs to customize the cell, the realization form here is tableView, but there is no reuse of tableView, at this point, if you want to save memory, you can reuse two or three TableViews. Here do not introduce more, there are relevant learning materials on the net, do not understand can go to study.
Since most network requests are the same, I use a common class TopViewController for the list layout
extension TopViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.topicArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "topic".for: indexPath) as! TopicCell
cell.topicModel = viewModel.topicArray[indexPath.row] as! TopicModel
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let topicModel = viewModel.topicArray[indexPath.row] as! TopicModel
return topicModel.cellHeight
}
}
Copy the code
I put the network request in a class called TopicViewModel, and the controller just passes in the page argument and the tableView, and nothing else
func loadTopicModel() {
viewModel.topicArray.removeAllObjects()
viewModel.page = 0
viewModel.loadTopicDataFromNet()
}
func loadMoreTopicModel() {
viewModel.page += 1
tableView.mj_footer.beginRefreshing()
viewModel.loadTopicDataFromNet()
}
Copy the code
This technique and I search on the net of some great god to MVVM demo some like, but I think this is not a real MVVM, just made some thin body, the controller should remain essentially belongs to the MVC, in recommended attention on this in the module I will roughly said about the concrete method of thin body, there is directly on the code snippet:
func loadTopicDataFromNet() {
var parameters = [String:Any]()
parameters["a"] = a
parameters["c"] = "data"
parameters["type"] = type
parameters["page"] = page
ifmaxtime ! = nil && (maxtime? .characters.count)! > 0 { parameters["maxtime"] = maxtime
}
self.parameters = parameters as NSDictionary?
NetWorkTool.NetWorkToolGet(urlString: "http://api.budejie.com/api/api_open.php", parameters: parameters, success: { (responseObj) in
ifself.parameters ! = (parameters as NSDictionary?) {return} self.maxtime = (responseObj? ["info"] as! [String:Any])["maxtime"] as! String?
var array = [Any]()
for topicModel inTopicModel.mj_objectArray(withKeyValuesArray: responseObj! ["list"]) {
array.append(topicModel)
}
self.topicArray.addObjects(from: array)
self.tableView.reloadData()
self.tableView.mj_header.endRefreshing()
self.tableView.mj_footer.endRefreshing()
}) { (error) in
ifself.parameters ! = (parameters as NSDictionary?) {return
}
self.tableView.mj_header.endRefreshing()
self.tableView.mj_footer.endRefreshing()
if self.page > 0 {
self.page -= 1
}
}
}
Copy the code
AFN is adopted for network request. At the beginning, I wanted to use the framework Alamofire to write, but it was not very easy to use, so I still used the commonly used AFN, and MJExtension was used for data parsing. The method is similar to OC, but Swift has strict requirements on data type. In the use of time need to pay more attention, according to the error of the corresponding modification, especially about the optional type and closure, it is recommended that you can thoroughly understand before writing, of course, can also understand while writing, I belong to the latter
2. Cell customization
Cell custom, mainly USES is combination of xib and code, the xib’s really convenient to use, to avoid a lot of the writing of the code, specific implementation can have a look at the download code, only on a few picture to display here, another thing to say is that the height of the cell, I was on the calculation and processing of model inside, this point, I didn’t know where to start when I first tried to do it this way, and then I asked the kid who wrote it, and he told me, and it’s like this, mainly didSet, because Swift, unlike OC, doesn’t have setter methods
var text:String? {
didSet {
ifCellHeight = = {0.0 cellHeight = ConstTool. Instance. TopicTextYlet maxSize = CGSize(width: ConstTool.instance.kScreenW - 4 * ConstTool.instance.TopicMargin, height: CGFloat(MAXFLOAT))
let textStr = NSString(string: text!)
let textH = textStr.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName:UIFont.systemFont(ofSize: 14)], context: nil).size.height
cellHeight += textH + ConstTool.instance.TopicMargin
let contentW = maxSize.width
var contentH = contentW * height / width
let contentX = ConstTool.instance.TopicMargin
let contentY = cellHeight
if type= = {10if contentH > ConstTool.instance.TopicPictureMaxH {
contentH = ConstTool.instance.TopicPictureDefaultH
hiddenSeebigButton = true
}else {
hiddenSeebigButton = false
}
pictureF = CGRect(x: contentX, y: contentY, width: contentW, height: contentH)
cellHeight += contentH + ConstTool.instance.TopicMargin
}else if (type == 31) {
voiceF = CGRect(x: contentX, y: contentY, width: contentW, height: contentH)
cellHeight += contentH + ConstTool.instance.TopicMargin
}else if(type == 41) {
videoF = CGRect(x: contentX, y: contentY, width: contentW, height: contentH)
cellHeight += contentH + ConstTool.instance.TopicMargin
}
iftop_cmt ! = nil {let commentContent = NSString(format: "% @ : % @", (top_cmt? .user? .username)! ,(top_cmt? .content)!)let commentContentH = commentContent.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName:UIFont.systemFont(ofSize: 13)], context: nil).size.height
cellHeight += commentContentH + ConstTool.instance.TopicMargin
}
cellHeight += ConstTool.instance.TopicBottomH + ConstTool.instance.TopicMargin
}
}
}
Copy the code
This can be understood in terms of Setter methods for OC
Here are some screenshots:
Click the plus button to create an animated display of the release interface. This effect looks cool, but it is not too difficult to implement, using a third-party library called POP
The main code is as follows:
func setupButtons() {
let maxCols = 3
let buttonW = 72
let buttonH = buttonW + 30
let buttonStartX = 15
let buttonMargx = (ConstTool.instance.kScreenW - CGFloat(maxCols * buttonW) - 2 * CGFloat(buttonStartX)) / CGFloat(maxCols - 1)
let buttonStartY = (ConstTool.instance.kScreenH - 2 * CGFloat(buttonH)) / CGFloat(2)
for i in 0 ..< imagesArr.count {
let button = VerticalButton(type: .custom)
let row = i / maxCols
let col = i % maxCols
let buttonX = CGFloat(buttonStartX) + (buttonMargx + CGFloat(buttonW)) * CGFloat(col)
let buttonEndY = buttonStartY + CGFloat(row) * CGFloat(buttonH)
button.setImage(UIImage(named:imagesArr[i]), for: .normal)
button.setTitle(titlesArr[i], for: .normal)
button.setTitleColor(UIColor.black, for: .normal) button.titleLabel? .font = UIFont.systemFont(ofSize: 12) addSubview(button) button.addTarget(self, action:#selector(PublishView.clickButton(button:)), for: .touchUpInside)
button.tag = i
letanimation = POPSpringAnimation(propertyNamed: kPOPViewFrame) animation? .fromValue = NSValue.init(cgRect: CGRect(x: buttonX, y: CGFloat(buttonH) - ConstTool.instance.kScreenH, width: CGFloat(buttonW), height: CGFloat(buttonH))) animation?.toValue = NSValue.init(cgRect: CGRect(x: buttonX, y: CGFloat(buttonEndY), width: CGFloat(buttonW), height: CGFloat(buttonH))) animation?.springBounciness = 5 animation?.springSpeed = 15 animation?.beginTime = CACurrentMediaTime() + CFTimeInterval(0.1 * CGFloat(I)) button.pop_add(animation,forKey: nil)
}
sloganView = UIImageView(image: UIImage(named: "app_slogan"))
addSubview(sloganView)
letCenterX: CGFloat = ConstTool. Instance. KScreenW * 0.5letCenterEndY: CGFloat = ConstTool. Instance. KScreenH * 0.2let centerBeginY:CGFloat = centerEndY - ConstTool.instance.kScreenH
let animation = POPSpringAnimation(propertyNamed: kPOPViewCenter)
animation?.fromValue = NSValue.init(cgPoint: CGPoint(x: centerX, y: centerBeginY))
animation?.toValue = NSValue.init(cgPoint: CGPoint(x: centerX, y: centerEndY))
animation?.springBounciness = 5
animation?.springSpeed = 15
animation?.beginTime = CACurrentMediaTime() + CFTimeInterval(0.1 * CGFloat(imagesArr.count))
animation?.completionBlock = { (animation,finished) in
self.isUserInteractionEnabled = true
}
sloganView.pop_add(animation, forKey: nil)
}
Copy the code
Interested colleagues can download the source code to take a closer look at Pop, which is available on Github
# 4 Login module
This module does not implement the login function, just the layout of the login and registration interface, mainly using the XIB layout
Click the register account to animate the register interface, mainly by changing the constraints on the left side of the login interface
@IBAction func shouRegisterView(_ sender: UIButton) {
if leftConstraint.constant == 0 {
leftConstraint.constant = -view.width
sender.isSelected = true
}else {
leftConstraint.constant = 0
sender.isSelected = falseAnimate (withDuration: 0.3, animations: animation)} funcanimation() {
self.view.layoutIfNeeded()
}
Copy the code
Five recommended attention modules
In this module I want to show you the implementation of pulling out the tableView, which is very possible to slim down the controller
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
navigationItem.title = "Recommended attention"
view.backgroundColor = ConstTool.instance.GlobalColor()
automaticallyAdjustsScrollViewInsets = false
categoryView.contentInset = UIEdgeInsets(top: 64, left: 0, bottom: 0, right: 0)
categoryListView.contentInset = UIEdgeInsets(top: 64, left: 0, bottom: 0, right: 0)
categoryView.delegate = self.manager
categoryListView.delegate = self.manager
categoryListView.dataSource = self.manager
categoryView.dataSource = self.manager
self.viewModel.categoryView = categoryView
self.viewModel.categoryListView = categoryListView
self.manager.viewModel = self.viewModel
self.viewModel.getRecommendCategoryDataFromNet()
}
Copy the code
These are the only lines of code I recommend watching your controller. Isn’t that refreshing? I’m also pretty cool that I’ve pulled out the tableView’s delegate method and datasource method and put them in a Manager class that inherits from NSObject, and set the tableView’s delegate and datasource to Manager
In the Manager, you associate a class that manages the data, called the viewModel, with the data source and UI interaction relying on these two classes. The controller can be almost completely isolated, which reduces coupling and makes it easy to modify
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView.tag == 1 {
return viewModel.categoryArray.count
}
tableView.mj_footer.isHidden = (viewModel.categoryListArray.count == 0)
return viewModel.categoryListArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView.tag == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "categoryCell".for: indexPath) as! CategoryCell
cell.categoryModel = viewModel.categoryArray[indexPath.row] as! CategoryModel
return cell
}
let cell = tableView.dequeueReusableCell(withIdentifier: "categoryListCell".for: indexPath) as! CategoryListCell
cell.categoryListModel = viewModel.categoryListArray[indexPath.row] as! CategoryListModel
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if tableView.tag == 2 {
return80}return 44
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.tag == 1 {
viewModel.categoryListView.mj_footer.endRefreshing()
let model = viewModel.categoryArray[indexPath.row] as! CategoryModel
viewModel.page = 1
viewModel.getRecommendCategoryListDataFromNetWithCategoryId(ID: model.ID)
}
}
Copy the code
The viewModel is another class, as follows:
func getRecommendCategoryDataFromNet() {
SVProgressHUD.show()
var parameters = [String:Any]()
parameters["a"] = "category"
parameters["c"] = "subscribe"
let manager = AFHTTPSessionManager()
manager.get("http://api.budejie.com/api/api_open.php", parameters: parameters, progress: {(progress) in
}, success: { (task, responseObj) in
SVProgressHUD.dismiss()
ifresponseObj ! = nil {let response = responseObj as! [String:Any]
self.categoryArray = CategoryModel.mj_objectArray(withKeyValuesArray: response["list"])
self.categoryView.reloadData()
let indexPath = NSIndexPath(row: 0, section: 0)
self.categoryView.selectRow(at: indexPath as IndexPath, animated: false, scrollPosition: .top)
let model = self.categoryArray[0] as! CategoryModel
self.getRecommendCategoryListDataFromNetWithCategoryId(ID: model.ID)
}
}, failure: {(task, error ) in
SVProgressHUD.showError(withStatus: "Failed to load classified data")})}Copy the code
When the data comes back, you just need tableView.reloadData() to change the layout
The specific implementation is like this, but if the processing is more complex logic, this may not be so simple, I feel more suitable for tableView to show the type of data, this way can try more
This module is relatively simple, I will not do more description here, see the demo for details, attached below a picture:
I have uploaded the specific demo to gitHub, and interested colleagues can download it. Because it is the first time to write code with Swift, many aspects are not in place and the function implementation is relatively simple. If there is a chance later, I will improve the function processing:
Swift version best not sister link address